- 浏览: 232915 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
shuhucy:
必须赞啊,源码理解的很深,解决一个困扰两天的问题
Spring AOP源码分析(八)SpringAOP要注意的地方 -
sealinesu:
精彩
Spring事务源码分析(一)Spring事务入门 -
whlt20090509:
"WEB-INF/view目录下有一个简单的hell ...
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门 -
hai0378:
兄台 算我一个,最近在研究dubbo motan 及 zk ...
ZooKeeper源码研究寻求小伙伴 -
zpkbtzmbw:
看懂了,原理
SpringMVC源码总结(五)Tomcat的URIEncoding、useBodyEncodingForURI和CharacterEncodingFilter
SpringMVC源码总结(七)mvc:annotation-driven中的HttpMessageConverter
- 博客分类:
- SpringMVC源码分析
这一篇文章主要介绍下HttpMessageConverter整个注册过程包含自定义的HttpMessageConverter,然后对一些HttpMessageConverter进行具体介绍。
HttpMessageConverter接口介绍:
从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解析的AnnotationDrivenBeanDefinitionParser中,有这么一个方法:
获取所有的HttpMessageConverter,最终设置到RequestMappingHandlerAdapter的private List<HttpMessageConverter<?>> 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来处理,然后就是如下的写入:
选取一个合适的content-type,再由这个content-type和返回类型来选取合适的HttpMessageConverter,找到合适的HttpMessageConverter后,便调用它的write方法。
接下来就说一说一些具体的HttpMessageConverter。
AbstractHttpMessageConverter:提供了进一步的抽象,将是否支持相应的MediaType这一共有的功能实现,它的子类只需关心是否支持返回类型。
AbstractHttpMessageConverter子类-StringHttpMessageConverter:如用于处理字符串到response中,这就要涉及编码问题,这一过程在本系列的第四篇文章中做过详细说明,这里跳过。
AbstractHttpMessageConverter子类-ByteArrayHttpMessageConverter:
源码就很清晰明了。它专门负责byte[]类型的转换。
AbstractHttpMessageConverter子类-MappingJacksonHttpMessageConverter:用于转换Object到json字符串类型。已过时,使用的是http://jackson.codehaus.org中Jackson 1.x的ObjectMapper,取代者为MappingJackson2HttpMessageConverter。依赖为:
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包:
接下来便说道:在注册HttpMessageConverter过程中的一些问题:
这段代码是在注册默认的HttpMessageConverter,但是个别HttpMessageConverter也是有条件的。即相应的jar包存在,才会去注册它。如MappingJackson2HttpMessageConverter,if (jackson2Present) {
messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));当jackson2Present为true时才会注册。而jackson2Present的值如下:
也就是当com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator存在在classpath中才会去加载MappingJackson2HttpMessageConverter。
同理,MappingJacksonHttpMessageConverter的判断如下:
所以当我们程序没法转换json时,你就需要考虑是否已经把MappingJacksonHttpMessageConverter或者MappingJackson2HttpMessageConverter的依赖加进来了,官方推荐使用MappingJackson2HttpMessageConverter。
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。
发表评论
-
SpringMVC源码总结(十二)ViewResolver介绍
2014-09-10 06:43 2180首先我们先看看ModelAndView中重要的View接口。 ... -
SpringMVC源码总结(十一)mvc:interceptors拦截器介绍
2014-09-08 20:21 5678本文章针对mvc:interceptors标签进行介绍,它的注 ... -
SpringMVC源码总结(十)自定义HandlerMethodArgumentResolver
2014-09-04 07:45 7275上一篇文章介绍了HandlerMethodArgumentRe ... -
SpringMVC源码总结(九)HandlerMethodArgumentResolver介绍
2014-09-02 06:24 12303本文章主要介绍HandlerMethodArgumentRes ... -
SpringMVC源码总结(八)类型转换PropertyEditor的背后
2014-08-30 17:13 4768PropertyEditor是Spring最初 ... -
SpringMVC源码总结(六)mvc:annotation-driven中的HandlerMethodReturnValueHandler
2014-08-26 06:21 6419经过了两篇的乱码说明,要重新回到mvc:annotation- ... -
SpringMVC源码总结(五)Tomcat的URIEncoding、useBodyEncodingForURI和CharacterEncodingFilter
2014-08-22 06:32 7175继续上一章节的乱码问题。上一篇文章仅仅说了设置Tomcat的U ... -
SpringMVC源码总结(四)由StringHttpMessageConverter引出的客户端服务器端之间的乱码过程分析
2014-08-20 22:49 3558继续上一篇文章遗留的乱码问题,引出从客户端数据到服务器端的乱码 ... -
SpringMVC源码总结(三)mvc:annotation-driven和mvc:message-converters简单介绍
2014-08-19 06:58 10058上一篇文章讲述了最简单的mvc:annotation-driv ... -
SpringMVC源码总结(二)mvc:annotation-driven以及@Controller和@RequestMapping的那些事
2014-08-16 22:47 8531上一篇文章让我们了解HandlerMapping和Handle ... -
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门
2014-08-16 22:42 14588刚接触SpringMVC,对它的xml文件配置一直比较模模糊糊 ...
相关推荐
NULL 博文链接:https://lgbolgger.iteye.com/blog/2105151
NULL 博文链接:https://yihuawuye1.iteye.com/blog/2105063
SpringMVC第12讲:<mvc:annotation-driven/>
<mvc:annotation-driven> </mvc:annotation-driven> 中配置Json格式乱码。代码如下: <!--json格式乱码处理--> <mvc:message-converters register-defaults="true"> <constructor-arg value="UTF-8"/>
赠送jar包:spring-webmvc-5.3.10.jar; 赠送原API文档:spring-webmvc-5.3.10-javadoc.jar; 赠送源代码:spring-webmvc-5.3.10-sources.jar; 赠送Maven依赖信息文件:spring-webmvc-5.3.10.pom; 包含翻译后的API...
SpringMVC ---- HelloWorld ---- 代码 SpringMVC ---- HelloWorld ---- 代码 SpringMVC ---- HelloWorld ---- 代码 SpringMVC ---- HelloWorld ---- 代码 SpringMVC ---- HelloWorld ---- 代码 SpringMVC ---- Hello...
<mvc:annotation-driven/> <!--视图解析--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> ...
一、前言 二、spring mvc 核心类与接口 三、spring mvc 核心流程图 四、spring mvc ...十九、 <mvc:annotation-driven /> 到底做了什么工作 二十、 本文中springMVC.xml配置文件是核心,这里给一个下载地址
SpringMVC-Mybatis-Shiro-redis-master..............
springMVC3学习(七)--Interceptor拦截器(源码) 文章地址:http://blog.csdn.net/itmyhome1990/article/details/26286107
SpringMVC: 一、前言 二、spring mvc 核心类与接口 三、spring mvc 核心流程图 ...十九、 <mvc:annotation-driven /> 到底做了什么工作 二十、 本文中springMVC.xml配置文件是核心,这里给一个下载地址
SpringMVC源码剖析(五)-消息转换器HttpMessageConverter1
springMVC相关技术配置使用注解的HandlerMapping和HandlerAdapter使用<mvc:annotation-driver> 不过springBoot已经省略了这些配置 配置使用注解的Handler和Service等等使用<context:component-scan> 不过springBoot...
Spring MVC 教程 快速入门 深入分析 目录 一、前言 二、spring mvc 核心类与接口 三、spring mvc 核心流程图 四、spring mvc DispatcherServlet说明 ...十九、 <mvc:annotation-driven /> 到底做了什么工作
个人认为相当适合入门和知识巩固!! 一、前言 二、spring mvc 核心类与接口 ...十九、 <mvc:annotation-driven /> 到底做了什么工作 二十、 本文中springMVC.xml配置文件是核心,这里给一个下载地址
Java项目源码互助 Spring MVC-RAML 插件 Spring MVC-RAML 项目旨在为使用 Spring MVC 框架的项目强制执行契约优先方法。...这个想法是手动维护 ...SpringMVC-RAML 插件使用基于构建系统。 先决条件 和 确保您的JAVA_HOM
spring-mvc.xml 配置自动扫描的包、配置视图解析器 如何把 handler方法返回值解析为实际的物理视图、配置静态资源映射静态资源交给默认的Servlet、配置 mvc:annotation-driven标签开启注解(4)@RestController =@...
springMVC3学习(六)--SimpleFormController(源码) 文章地址:http://blog.csdn.net/itmyhome1990/article/details/25988733
springMVC3学习(八)--全局的异常处理(源码) 文章地址:http://blog.csdn.net/itmyhome1990/article/details/26286435
springMVC3学习(五)--MultiActionController(源码) 文章地址:http://blog.csdn.net/itmyhome1990/article/details/25988091