SpringMVC之HttpMessageConverter

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

HttpMessageConverter接口介绍:
 
Java代码   收藏代码
  1. public interface HttpMessageConverter<T> {  
  2.   
  3.     /** 
  4.      * Indicates whether the given class can be read by this converter. 
  5.      * @param clazz the class to test for readability 
  6.      * @param mediaType the media type to read, can be {@code null} if not specified. 
  7.      * Typically the value of a {@code Content-Type} header. 
  8.      * @return {@code true} if readable; {@code false} otherwise 
  9.      */  
  10.     boolean canRead(Class<?> clazz, MediaType mediaType);  
  11.   
  12.     /** 
  13.      * Indicates whether the given class can be written by this converter. 
  14.      * @param clazz the class to test for writability 
  15.      * @param mediaType the media type to write, can be {@code null} if not specified. 
  16.      * Typically the value of an {@code Accept} header. 
  17.      * @return {@code true} if writable; {@code false} otherwise 
  18.      */  
  19.     boolean canWrite(Class<?> clazz, MediaType mediaType);  
  20.   
  21.     /** 
  22.      * Return the list of {@link MediaType} objects supported by this converter. 
  23.      * @return the list of supported media types 
  24.      */  
  25.     List<MediaType> getSupportedMediaTypes();  
  26.   
  27.     /** 
  28.      * Read an object of the given type form the given input message, and returns it. 
  29.      * @param clazz the type of object to return. This type must have previously been passed to the 
  30.      * {@link #canRead canRead} method of this interface, which must have returned {@code true}. 
  31.      * @param inputMessage the HTTP input message to read from 
  32.      * @return the converted object 
  33.      * @throws IOException in case of I/O errors 
  34.      * @throws HttpMessageNotReadableException in case of conversion errors 
  35.      */  
  36.     T read(Class<? extends T> clazz, HttpInputMessage inputMessage)  
  37.             throws IOException, HttpMessageNotReadableException;  
  38.   
  39.     /** 
  40.      * Write an given object to the given output message. 
  41.      * @param t the object to write to the output message. The type of this object must have previously been 
  42.      * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}. 
  43.      * @param contentType the content type to use when writing. May be {@code null} to indicate that the 
  44.      * default content type of the converter must be used. If not {@code null}, this media type must have 
  45.      * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have 
  46.      * returned {@code true}. 
  47.      * @param outputMessage the message to write to 
  48.      * @throws IOException in case of I/O errors 
  49.      * @throws HttpMessageNotWritableException in case of conversion errors 
  50.      */  
  51.     void write(T t, MediaType contentType, HttpOutputMessage outputMessage)  
  52.             throws IOException, HttpMessageNotWritableException;  
  53.   
  54. }  

从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。 
看下自定义的使用方式:
 
Java代码   收藏代码
  1. <mvc:annotation-driven>  
  2.         <mvc:message-converters register-defaults="true">  
  3.             <bean class="org.springframework.http.converter.StringHttpMessageConverter">  
  4.                 <constructor-arg value="UTF-8"/>  
  5.             </bean>  
  6.         </mvc:message-converters>  
  7.     </mvc:annotation-driven>  

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

获取所有的HttpMessageConverter,最终设置到RequestMappingHandlerAdapter的private List<HttpMessageConverter<?>> messageConverters属性上。看下具体的获取过程:  
Java代码   收藏代码
  1. private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {  
  2.         Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");  
  3.         ManagedList<? super Object> messageConverters = new ManagedList<Object>();  
  4.         if (convertersElement != null) {  
  5.             messageConverters.setSource(source);  
  6.             for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean""ref")) {  
  7.                 Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);  
  8.                 messageConverters.add(object);  
  9.             }  
  10.         }  
  11.   
  12.         if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {  
  13.             messageConverters.setSource(source);  
  14.             messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));  
  15.   
  16.             RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);  
  17.             stringConverterDef.getPropertyValues().add("writeAcceptCharset"false);  
  18.             messageConverters.add(stringConverterDef);  
  19.   
  20.             messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));  
  21.             messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));  
  22.             messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));  
  23.   
  24.             if (romePresent) {  
  25.                 messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));  
  26.                 messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));  
  27.             }  
  28.             if (jaxb2Present) {  
  29.                 messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));  
  30.             }  
  31.             if (jackson2Present) {  
  32.                 messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));  
  33.             }  
  34.             else if (jacksonPresent) {  
  35.                 messageConverters.add(createConverterDefinition(  
  36.                         org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));  
  37.             }  
  38.         }  
  39.         return messageConverters;  
  40.     }  

该过程第一步: 
解析并获取我们自定义的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来处理,然后就是如下的写入:
 
Java代码   收藏代码
  1. protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,  
  2.             ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)  
  3.             throws IOException, HttpMediaTypeNotAcceptableException {  
  4.   
  5.         Class<?> returnValueClass = returnValue.getClass();  
  6.         HttpServletRequest servletRequest = inputMessage.getServletRequest();  
  7.         List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);  
  8.         List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);  
  9.   
  10.         Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();  
  11.         for (MediaType requestedType : requestedMediaTypes) {  
  12.             for (MediaType producibleType : producibleMediaTypes) {  
  13.                 if (requestedType.isCompatibleWith(producibleType)) {  
  14.                     compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));  
  15.                 }  
  16.             }  
  17.         }  
  18.         if (compatibleMediaTypes.isEmpty()) {  
  19.             throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);  
  20.         }  
  21.   
  22.         List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);  
  23.         MediaType.sortBySpecificityAndQuality(mediaTypes);  
  24.   
  25.         MediaType selectedMediaType = null;  
  26.         for (MediaType mediaType : mediaTypes) {  
  27.             if (mediaType.isConcrete()) {  
  28.                 selectedMediaType = mediaType;  
  29.                 break;  
  30.             }  
  31.             else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {  
  32.                 selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;  
  33.                 break;  
  34.             }  
  35.         }  
  36.   
  37.         if (selectedMediaType != null) {  
  38.             selectedMediaType = selectedMediaType.removeQualityValue();  
  39.             for (HttpMessageConverter<?> messageConverter : this.messageConverters) {  
  40.                 if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {  
  41.                     ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);  
  42.                     if (logger.isDebugEnabled()) {  
  43.                         logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +  
  44.                                 messageConverter + "]");  
  45.                     }  
  46.                     return;  
  47.                 }  
  48.             }  
  49.         }  
  50.         throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);  
  51.     }  

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

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

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

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

AbstractHttpMessageConverter子类-ByteArrayHttpMessageConverter:
 
Java代码   收藏代码
  1. public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<byte[]> {  
  2.   
  3.     /** Creates a new instance of the {@code ByteArrayHttpMessageConverter}. */  
  4.     public ByteArrayHttpMessageConverter() {  
  5.         super(new MediaType("application""octet-stream"), MediaType.ALL);  
  6.     }  
  7.   
  8.     @Override  
  9.     public boolean supports(Class<?> clazz) {  
  10.         return byte[].class.equals(clazz);  
  11.     }  
  12.   
  13.     @Override  
  14.     public byte[] readInternal(Class<? extends byte[]> clazz, HttpInputMessage inputMessage) throws IOException {  
  15.         long contentLength = inputMessage.getHeaders().getContentLength();  
  16.         ByteArrayOutputStream bos =  
  17.                 new ByteArrayOutputStream(contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);  
  18.         StreamUtils.copy(inputMessage.getBody(), bos);  
  19.         return bos.toByteArray();  
  20.     }  
  21.   
  22.     @Override  
  23.     protected Long getContentLength(byte[] bytes, MediaType contentType) {  
  24.         return (long) bytes.length;  
  25.     }  
  26.   
  27.     @Override  
  28.     protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException {  
  29.         StreamUtils.copy(bytes, outputMessage.getBody());  
  30.     }  
  31.   
  32. }  

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

AbstractHttpMessageConverter子类-MappingJacksonHttpMessageConverter:用于转换Object到json字符串类型。已过时,使用的是http://jackson.codehaus.org中Jackson 1.x的ObjectMapper,取代者为MappingJackson2HttpMessageConverter。依赖为:
 
Java代码   收藏代码
  1. <dependency>   
  2.         <groupId>org.codehaus.jackson</groupId>   
  3.         <artifactId>jackson-core-asl</artifactId>   
  4.         <version>1.9.11</version>   
  5.     </dependency>   
  6.       
  7.     <dependency>   
  8.         <groupId>org.codehaus.jackson</groupId>   
  9.         <artifactId>jackson-mapper-asl</artifactId>   
  10.         <version>1.9.11</version>   
  11.     </dependency>   
  12.       

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包:
 
Java代码   收藏代码
  1. <dependency>  
  2.     <dependency>  
  3.         <groupId>com.fasterxml.jackson.core</groupId>  
  4.         <artifactId>jackson-databind</artifactId>  
  5.         <version>2.4.2</version>   
  6.     </dependency>  

接下来便说道:在注册HttpMessageConverter过程中的一些问题:  
Java代码   收藏代码
  1. if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {  
  2.             messageConverters.setSource(source);  
  3.             messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));  
  4.   
  5.             RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);  
  6.             stringConverterDef.getPropertyValues().add("writeAcceptCharset"false);  
  7.             messageConverters.add(stringConverterDef);  
  8.   
  9.             messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));  
  10.             messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));  
  11.             messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));  
  12.   
  13.             if (romePresent) {  
  14.                 messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));  
  15.                 messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));  
  16.             }  
  17.             if (jaxb2Present) {  
  18.                 messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));  
  19.             }  
  20.             if (jackson2Present) {  
  21.                 messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));  
  22.             }  
  23.             else if (jacksonPresent) {  
  24.                 messageConverters.add(createConverterDefinition(  
  25.                         org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));  
  26.             }  
  27.         }  

这段代码是在注册默认的HttpMessageConverter,但是个别HttpMessageConverter也是有条件的。即相应的jar包存在,才会去注册它。如MappingJackson2HttpMessageConverter,if (jackson2Present) { 
messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));当jackson2Present为true时才会注册。而jackson2Present的值如下:
 
Java代码   收藏代码
  1. private static final boolean jackson2Present =  
  2.             ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&  
  3.                     ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());  

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

同理,MappingJacksonHttpMessageConverter的判断如下:
 
Java代码   收藏代码
  1. private static final boolean jacksonPresent =  
  2.             ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&  
  3.                     ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());  


所以当我们程序没法转换json时,你就需要考虑是否已经把MappingJacksonHttpMessageConverter或者MappingJackson2HttpMessageConverter的依赖加进来了,官方推荐使用MappingJackson2HttpMessageConverter。
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
扩展Spring MVC可以通过添加自定义组件、拦截器、视图解析器等方式来增强框架的功能和灵活性。下面列举了一些扩展Spring MVC的常见方式: 1. 自定义注解:可以通过自定义注解来标记特定的Controller、方法或参数,以实现自定义的请求映射和处理逻辑。 2. 自定义拦截器:通过实现HandlerInterceptor接口来编写自定义拦截器,可以在请求处理前后进行一些预处理或后处理操作。 3. 自定义参数解析器:实现HandlerMethodArgumentResolver接口,可以将请求中的特定参数解析为自定义类型,可以用于处理特殊的参数绑定逻辑。 4. 自定义返回值处理器:实现HandlerMethodReturnValueHandler接口,可以将方法返回值转换为特定的响应格式,例如将返回值转换为JSON格式。 5. 自定义视图解析器:实现ViewResolver接口,可以根据请求的特定条件选择合适的视图进行渲染。 6. 自定义异常处理器:通过实现HandlerExceptionResolver接口,可以统一处理应用程序中的异常,并返回特定的错误响应。 7. 扩展数据绑定:通过实现WebDataBinder的CustomEditorConfigurer接口,可以扩展数据绑定功能,例如将特定的字符串格式转换为自定义类型。 8. 扩展消息转换器:通过实现HttpMessageConverter接口,可以自定义消息转换器,用于处理请求和响应的内容类型,例如将XML转换为对象或将对象转换为XML。 9. 扩展文件上传:通过MultipartResolver接口的实现类,可以扩展文件上传功能,例如限制上传文件的大小、类型等。 以上只是一些常见的扩展方式,实际上Spring MVC框架非常灵活,可以根据具体需求进行定制和扩展。通过理解框架的核心原理和接口,你可以根据自己的需求进行扩展,以满足项目的特定要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值