`

SpringMVC源码总结(七)mvc:annotation-driven中的HttpMessageConverter

阅读更多
这一篇文章主要介绍下HttpMessageConverter整个注册过程包含自定义的HttpMessageConverter,然后对一些HttpMessageConverter进行具体介绍。

HttpMessageConverter接口介绍:

public interface HttpMessageConverter<T> {

	/**
	 * Indicates whether the given class can be read by this converter.
	 * @param clazz the class to test for readability
	 * @param mediaType the media type to read, can be {@code null} if not specified.
	 * Typically the value of a {@code Content-Type} header.
	 * @return {@code true} if readable; {@code false} otherwise
	 */
	boolean canRead(Class<?> clazz, MediaType mediaType);

	/**
	 * Indicates whether the given class can be written by this converter.
	 * @param clazz the class to test for writability
	 * @param mediaType the media type to write, can be {@code null} if not specified.
	 * Typically the value of an {@code Accept} header.
	 * @return {@code true} if writable; {@code false} otherwise
	 */
	boolean canWrite(Class<?> clazz, MediaType mediaType);

	/**
	 * Return the list of {@link MediaType} objects supported by this converter.
	 * @return the list of supported media types
	 */
	List<MediaType> getSupportedMediaTypes();

	/**
	 * Read an object of the given type form the given input message, and returns it.
	 * @param clazz the type of object to return. This type must have previously been passed to the
	 * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
	 * @param inputMessage the HTTP input message to read from
	 * @return the converted object
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotReadableException in case of conversion errors
	 */
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	/**
	 * Write an given object to the given output message.
	 * @param t the object to write to the output message. The type of this object must have previously been
	 * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
	 * @param contentType the content type to use when writing. May be {@code null} to indicate that the
	 * default content type of the converter must be used. If not {@code null}, this media type must have
	 * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
	 * returned {@code true}.
	 * @param outputMessage the message to write to
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotWritableException in case of conversion errors
	 */
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

从HttpInputMessage中读取数据: T read(Class<? extends T> clazz, HttpInputMessage inputMessage),前提clazz能够通过canRead(clazz,mediaType)测试。
向HttpOutputMessage中写入数据:void write(T t, MediaType contentType, HttpOutputMessage outputMessage),前提能够通过canWrite方法。

简单举例:
如StringHttpMessageConverter,read方法就是根据编码类型将HttpInputMessage中的数据变为字符串。write方法就是根据编码类型将字符串数据写入HttpOutputMessage中。

HttpMessageConverter的使用场景:
它主要是用来转换request的内容到一定的格式,转换输出的内容的到response。
看下自定义的使用方式:

<mvc:annotation-driven>
		<mvc:message-converters register-defaults="true">
			<bean class="org.springframework.http.converter.StringHttpMessageConverter">
				<constructor-arg value="UTF-8"/>
			</bean>
		</mvc:message-converters>
	</mvc:annotation-driven>

首先还是在对mvc:annotation-driven解析的AnnotationDrivenBeanDefinitionParser中,有这么一个方法:
ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);

获取所有的HttpMessageConverter,最终设置到RequestMappingHandlerAdapter的private List<HttpMessageConverter<?>> messageConverters属性上。看下具体的获取过程:
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
		Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
		ManagedList<? super Object> messageConverters = new ManagedList<Object>();
		if (convertersElement != null) {
			messageConverters.setSource(source);
			for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
				Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
				messageConverters.add(object);
			}
		}

		if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
			messageConverters.setSource(source);
			messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

			RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
			stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
			messageConverters.add(stringConverterDef);

			messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
			messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
			messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

			if (romePresent) {
				messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
				messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
			}
			if (jaxb2Present) {
				messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
			}
			if (jackson2Present) {
				messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
			}
			else if (jacksonPresent) {
				messageConverters.add(createConverterDefinition(
						org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
			}
		}
		return messageConverters;
	}


该过程第一步:
解析并获取我们自定义的HttpMessageConverter,
该过程第二步:
<mvc:message-converters register-defaults="true">有一个register-defaults属性,当为true时,仍然注册默认的HttpMessageConverter,当为false则不注册,仅仅使用用户自定义的HttpMessageConverter。

获取完毕,便会将这些HttpMessageConverter设置进RequestMappingHandlerAdapter的messageConverters属性中。

然后就是它的使用过程,HttpMessageConverter主要针对那些不会返回view视图的response:
即含有方法含有@ResponseBody或者返回值为HttpEntity等类型的,它们都会用到HttpMessageConverter。以@ResponseBody举例:
首先先决定由哪个HandlerMethodReturnValueHandler来处理返回值,由于是@ResponseBody所以将会由RequestResponseBodyMethodProcessor来处理,然后就是如下的写入:

protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException {

		Class<?> returnValueClass = returnValue.getClass();
		HttpServletRequest servletRequest = inputMessage.getServletRequest();
		List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
		List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);

		Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
		for (MediaType requestedType : requestedMediaTypes) {
			for (MediaType producibleType : producibleMediaTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
		if (compatibleMediaTypes.isEmpty()) {
			throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
		}

		List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
		MediaType.sortBySpecificityAndQuality(mediaTypes);

		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypes) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
				if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
					((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
					if (logger.isDebugEnabled()) {
						logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
								messageConverter + "]");
					}
					return;
				}
			}
		}
		throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
	}

选取一个合适的content-type,再由这个content-type和返回类型来选取合适的HttpMessageConverter,找到合适的HttpMessageConverter后,便调用它的write方法。

接下来就说一说一些具体的HttpMessageConverter。

AbstractHttpMessageConverter:提供了进一步的抽象,将是否支持相应的MediaType这一共有的功能实现,它的子类只需关心是否支持返回类型。

AbstractHttpMessageConverter子类-StringHttpMessageConverter:如用于处理字符串到response中,这就要涉及编码问题,这一过程在本系列的第四篇文章中做过详细说明,这里跳过。

AbstractHttpMessageConverter子类-ByteArrayHttpMessageConverter:

public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<byte[]> {

	/** Creates a new instance of the {@code ByteArrayHttpMessageConverter}. */
	public ByteArrayHttpMessageConverter() {
		super(new MediaType("application", "octet-stream"), MediaType.ALL);
	}

	@Override
	public boolean supports(Class<?> clazz) {
		return byte[].class.equals(clazz);
	}

	@Override
	public byte[] readInternal(Class<? extends byte[]> clazz, HttpInputMessage inputMessage) throws IOException {
		long contentLength = inputMessage.getHeaders().getContentLength();
		ByteArrayOutputStream bos =
				new ByteArrayOutputStream(contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);
		StreamUtils.copy(inputMessage.getBody(), bos);
		return bos.toByteArray();
	}

	@Override
	protected Long getContentLength(byte[] bytes, MediaType contentType) {
		return (long) bytes.length;
	}

	@Override
	protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException {
		StreamUtils.copy(bytes, outputMessage.getBody());
	}

}

源码就很清晰明了。它专门负责byte[]类型的转换。

AbstractHttpMessageConverter子类-MappingJacksonHttpMessageConverter:用于转换Object到json字符串类型。已过时,使用的是http://jackson.codehaus.org中Jackson 1.x的ObjectMapper,取代者为MappingJackson2HttpMessageConverter。依赖为:

<dependency> 
		<groupId>org.codehaus.jackson</groupId> 
		<artifactId>jackson-core-asl</artifactId> 
		<version>1.9.11</version> 
	</dependency> 
	
	<dependency> 
		<groupId>org.codehaus.jackson</groupId> 
		<artifactId>jackson-mapper-asl</artifactId> 
		<version>1.9.11</version> 
	</dependency> 
	

AbstractHttpMessageConverter子类-MappingJackson2HttpMessageConverter:
它所使用的json转换器是http://jackson.codehaus.org中Jackson 2.x的ObjectMapper。
依赖的jar包为有3个,jackson-databind和它的两个依赖jackson-annotations、jackson-core,但是有了jackson-databind的pom文件会去自动下载它的依赖,所以只需增添jackson-databind的pom即可获取上述3个jar包:

<dependency>
	<dependency>
	    <groupId>com.fasterxml.jackson.core</groupId>
	    <artifactId>jackson-databind</artifactId>
	    <version>2.4.2</version> 
	</dependency>

接下来便说道:在注册HttpMessageConverter过程中的一些问题:
if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
			messageConverters.setSource(source);
			messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

			RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
			stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
			messageConverters.add(stringConverterDef);

			messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
			messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
			messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

			if (romePresent) {
				messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
				messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
			}
			if (jaxb2Present) {
				messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
			}
			if (jackson2Present) {
				messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
			}
			else if (jacksonPresent) {
				messageConverters.add(createConverterDefinition(
						org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
			}
		}

这段代码是在注册默认的HttpMessageConverter,但是个别HttpMessageConverter也是有条件的。即相应的jar包存在,才会去注册它。如MappingJackson2HttpMessageConverter,if (jackson2Present) {
messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));当jackson2Present为true时才会注册。而jackson2Present的值如下:

private static final boolean jackson2Present =
			ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
					ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

也就是当com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator存在在classpath中才会去加载MappingJackson2HttpMessageConverter。

同理,MappingJacksonHttpMessageConverter的判断如下:

private static final boolean jacksonPresent =
			ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
					ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());


所以当我们程序没法转换json时,你就需要考虑是否已经把MappingJacksonHttpMessageConverter或者MappingJackson2HttpMessageConverter的依赖加进来了,官方推荐使用MappingJackson2HttpMessageConverter。
分享到:
评论
2 楼 azhao2 2015-05-18  
1 楼 happylouis 2015-02-28  

相关推荐

    SpringMVC源码总结(三)mvc:annotation-driven和mvc:message-converters简单介绍

    在Spring MVC框架中,`mvc:annotation-driven`和`mvc:message-converters`是两个非常重要的元素,它们在处理基于注解的控制器和数据转换方面起着关键作用。本篇文章将深入探讨这两个组件的工作原理以及如何在实际...

    SpringMVC源码总结(二)mvc:mvc:annotation-driven背后的那些事

    在Spring MVC框架中,`mvc:annotation-driven`是Spring MVC配置中的一个重要元素,它使得我们的应用能够支持基于注解的控制器、数据绑定、格式化转换器和服务端验证等功能。这篇博客将深入探讨`mvc:annotation-...

    Spring源码学习十一:SpringMVC-@RequestBody接收json数据报4151

    深入源码分析,`&lt;mvc:annotation-driven /&gt;` 是Spring MVC中用于启用注解驱动的配置元素,它会自动配置一些关键组件,包括消息转换器。`MvcNamespaceHandler` 是处理这个注解的命名空间处理器,而`...

    一个简单的springMVC项目的配置文件 ---- 2016-05-31

    标题中的“一个简单的springMVC项目的配置文件”指的是基于Spring MVC框架构建的Web应用程序的配置文件,这通常包括XML配置、Java配置或者两者的结合。Spring MVC是Spring框架的一个模块,用于处理Web请求和响应,它...

    SPRING MVC3.2案例讲解---配置

    `mvc:annotation-driven`则是开启Spring MVC的注解驱动,支持我们在Controller方法上使用@RequestMapping等注解。 除了基本配置,我们还可以配置拦截器(Interceptor)、异常处理器(HandlerExceptionResolver)...

    SSM中通过Json做前后端分离示例源码

    SSM(Spring、SpringMVC、MyBatis)框架是Java Web开发中常见的技术栈,它结合了Spring的IOC(Inversion of Control)容器、SpringMVC作为控制器以及MyBatis作为持久层框架,提供了强大的功能和灵活性。在这个示例中...

    搭建springmvc环境源码.zip

    &lt;mvc:annotation-driven/&gt; &lt;!-- 视图解析器配置 --&gt; &lt;property name="prefix" value="/WEB-INF/views/" /&gt; ``` 然后,创建一个简单的Controller类,使用注解来处理HTTP请求。例如,一个处理GET请求的...

    springmvc开发笔记

    &lt;/mvc:annotation-driven&gt; ``` 3. **控制器中的编码配置**:还可以在控制器方法中显式指定返回数据的编码。 ```java @RequestMapping(value="/getWeather", method={RequestMethod.POST, RequestMethod.GET}, ...

    Eclipse配置SpringMVC源码

    在本文中,我们将深入探讨如何在Eclipse集成开发环境中配置SpringMVC的源码,以便进行深入学习和开发。SpringMVC是Spring框架的一部分,它为构建基于Java的Web应用程序提供了一个模型-视图-控制器(MVC)架构。...

    [spring 3.0] mvc 整合 restful 、maven实例 下载

    在本文中,我们将深入探讨如何在Spring 3.0中整合MVC框架与RESTful服务,并结合Maven构建项目。RESTful(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序,尤其适用于Web服务。Spring ...

    Springmvc-helloword简单例子

    &lt;mvc:annotation-driven/&gt; &lt;property name="prefix" value="/WEB-INF/views/"/&gt; ``` 4. **创建 Controller** 创建一个名为 `HelloController` 的 Java 类,使用 `@Controller` 注解标记为 MVC 控制器,...

    Spring和SpringMVC父子容器关系初窥(小结)

    在applicationContext.xml中配置了&lt;context:component-scan base-package="..." /&gt;,这个配置是扫描整个项目的包,而在applicationContext-MVC.xml中配置了&lt;mvc:annotation-driven /&gt;,这个配置是扫描SpringMVC的...

    springmvc简单工程搭建(源码)

    &lt;mvc:annotation-driven /&gt; &lt;context:component-scan base-package="com.example.springmvc"/&gt; ``` 接着,创建Controller类。使用@Controller注解标记该类为SpringMVC的控制器,并通过@RequestMapping注解来映射URL...

    eclipse 搭建SpringMVC框架一

    &lt;mvc:annotation-driven /&gt; &lt;property name="prefix" value="/WEB-INF/views/" /&gt; ``` 这里,`base-package`属性指定你的控制器类所在的包,`InternalResourceViewResolver`则配置了视图解析规则。 5. **...

    SpringMVC学习笔记+学习源码.zip

    14. **MVC注解驱动**:通过启用`@EnableWebMvc`或在配置文件中设置`&lt;mvc:annotation-driven&gt;`,可以启用SpringMVC的注解驱动,简化配置。 15. **SpringMVC与其他Spring组件的集成**:如与Spring AOP结合实现切面...

    spring mvc 项目源码实例 + 完整环境配置详细说明

    &lt;mvc:annotation-driven /&gt; &lt;property name="prefix" value="/WEB-INF/views/" /&gt; ``` #### 3. `applicationContext.xml` 这是 Spring 容器的配置文件,用于配置业务对象和服务: ```xml ...

    springmvc及mybatis框架的配置参数信息

    其中,`&lt;mvc:annotation-driven&gt;`元素开启注解驱动,使得SpringMVC可以识别@Controller、@RequestMapping等注解。`&lt;context:component-scan&gt;`用于扫描包含@Controller注解的类。 2. **视图解析器配置**:例如,我们...

    基于springMVC3.2的REST源码,结合了jquery和json

    1. **配置RESTful支持**:在SpringMVC的配置文件中启用`&lt;mvc:annotation-driven&gt;`元素,以支持@RequestMapping等注解。 2. **控制器(Controllers)**:使用@Controller和@RequestMapping注解定义处理HTTP请求的...

    Spring MVC快速入门

    &lt;mvc:annotation-driven/&gt; ``` - 如果使用 Spring Boot,配置可以简化为自动扫描控制器和启用 MVC 功能。 5. **编写控制器** - 创建 Java 类,如 `HelloController.java`,并添加 `@RestController` 或 `@...

Global site tag (gtag.js) - Google Analytics