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

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

HttpMessageConverter接口介绍:
 
Java代码   收藏代码
  1. public interface HttpMessageConverter {  
  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 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(Classextends 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 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.   
  2.         "true">  
  3.             class="org.springframework.http.converter.StringHttpMessageConverter">  
  4.                 "UTF-8"/>  
  5.               
  6.           
  7.       

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

获取所有的HttpMessageConverter,最终设置到RequestMappingHandlerAdapter的private List> messageConverters属性上。看下具体的获取过程:  
Java代码   收藏代码
  1. private ManagedList getMessageConverters(Element element, Object source, ParserContext parserContext) {  
  2.         Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");  
  3.         ManagedListsuper Object> messageConverters = new ManagedList();  
  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.     }  

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

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

    然后就是它的使用过程,HttpMessageConverter主要针对那些不会返回view视图的response: 

    即含有方法含有@ResponseBody或者返回值为HttpEntity等类型的,它们都会用到HttpMessageConverter。以@ResponseBody举例: 

    在Spring中要用注解就必须要在xml中配置两个bean,分别是RequestMappingHandlerAdapter和RequestMappingHandlerMapping两个类,前者是注册注解处理器的后者是进行注解映射的,里面都封装了Spring默认的一些注解处理器和返回值处理器

        private List getDefaultArgumentResolvers() {
            List resolvers = new ArrayList();
    
            // Annotation-based argument resolution
            resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
            resolvers.add(new RequestParamMapMethodArgumentResolver());
            resolvers.add(new PathVariableMethodArgumentResolver());
            resolvers.add(new PathVariableMapMethodArgumentResolver());
            resolvers.add(new MatrixVariableMethodArgumentResolver());
            resolvers.add(new MatrixVariableMapMethodArgumentResolver());
            resolvers.add(new ServletModelAttributeMethodProcessor(false));
            resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
    private List getDefaultReturnValueHandlers() {
            List handlers = new ArrayList();
    
            // Single-purpose return value types
            handlers.add(new ModelAndViewMethodReturnValueHandler());
            handlers.add(new ModelMethodProcessor());
            handlers.add(new ViewMethodReturnValueHandler());
            handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
            handlers.add(new HttpHeadersReturnValueHandler());
            handlers.add(new CallableMethodReturnValueHandler());
            handlers.add(new DeferredResultMethodReturnValueHandler());
            handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
    
            // Annotation-based return value types
            handlers.add(new ModelAttributeMethodProcessor(false));
            handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager));

    上面的代码是RequestMappingHandlerAdapter中Spring默认的注解处理器(resolvers)和返回值处理器(handlers)集合,RequestMappingHandlerAdapter和RequestMappingHandlerMapping都在org.springframework.web.servlet.mvc.method.annotation包下面, RequestMappingHandlerAdapter不仅仅是定义注解处理器和返回值处理器,还可以定义消息转换器(messageconverters)和视图模型转换器(modelAndViewResolvers)等等,大家可以自己去看一下代码, 


    首先先决定由哪个HandlerMethodReturnValueHandler来处理返回值,由于是@ResponseBody所以将会由RequestResponseBodyMethodProcessor来处理(上述代码中,红色行为该处理器注入了所有messageConverters),然后就是如下的写入:  
    Java代码   收藏代码
    1. protected  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 requestedMediaTypes = getAcceptableMediaTypes(servletRequest);  
    8.         List producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);  
    9.   
    10.         Set compatibleMediaTypes = new LinkedHashSet();  
    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 mediaTypes = new ArrayList(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) 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(Classextends 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.    
    2.         org.codehaus.jackson   
    3.         jackson-core-asl   
    4.         1.9.11   
    5.        
    6.       
    7.        
    8.         org.codehaus.jackson   
    9.         jackson-mapper-asl   
    10.         1.9.11   
    11.        
    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.   
    2.       
    3.         com.fasterxml.jackson.core  
    4.         jackson-databind  
    5.         2.4.2   
    6.       

    接下来便说道:在注册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。

    你可能感兴趣的:(SpringMvc-原理分析)