SpringMVC重要接口(三)HttpMessageConverter

来源:互联网 发布:java手游 编辑:程序博客网 时间:2024/05/18 17:40

引言:如何在SpringMVC中统一对返回的Json进行加密?”。

大部分人的第一反应是通过SpringMVC拦截器(Interceptor)中的postHandler方法处理。实际这是行不通的,因为当程序运行到该方法,是在返回数据之后,渲染页面之前,所以这时候HttpServletResponse中的输出流已经关闭了,自然无法在对返回数据进行处理。

其实这个问题用几行代码就可以搞定,因为SpringMVC提供了非常丰富的扩展支持,无论是之前提到的MethodArgumentResolverHandlerMethodReturnValueHandler,还是接下来要提到的HttpMessageConverter

在SpringMVC的 Controller层经常会用到@RequestBody@ResponseBody,通过这两个注解,可以在Controller中直接使用Java对象作为请求参数和返回内容,而完成这之间转换作用的便是HttpMessageConverter

这里写图片描述

这里写图片描述

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

HttpMessageConverter接口提供了5个方法:

  • canRead:判断该转换器是否能将请求内容转换成Java对象

  • canWrite:判断该转换器是否可以将Java对象转换成返回内容

  • getSupportedMediaTypes:获得该转换器支持的MediaType类型

  • read:读取请求内容并转换成Java对象

  • write:将Java对象转换后写入返回内容

其中readwrite方法的参数分别有有HttpInputMessageHttpOutputMessage对象,这两个对象分别代表着一次Http通讯中的请求和响应部分,可以通过getBody方法获得对应的输入流和输出流。



这里写图片描述

这里写图片描述

当前SpringMVC中已经默认提供了相当多的转换器,如上图,其中常用的有:


名称作用读支持MediaType写支持MediaTypeByteArrayHttpMessageConverter数据与字节数组的相互转换*/*application/octet-streamStringHttpMessageConverter数据与String类型的相互转换text/*text/plainFormHttpMessageConverter表单与MultiValueMap的相互转换application/x-www-form-urlencodedapplication/x-www-form-urlencodedSourceHttpMessageConverter数据与javax.xml.transform.Source的相互转换text/xml和application/xmltext/xml和application/xmlMarshallingHttpMessageConverter使用Spring的Marshaller/Unmarshaller转换XML数据text/xml和application/xmltext/xml和application/xmlMappingJackson2HttpMessageConverter使用Jackson的ObjectMapper转换Json数据application/jsonapplication/jsonMappingJackson2XmlHttpMessageConverter使用Jackson的XmlMapper转换XML数据application/xmlapplication/xmlBufferedImageHttpMessageConverter数据与java.awt.image.BufferedImage的相互转换Java I/O API支持的所有类型Java I/O API支持的所有类型


这里写图片描述


SpringMVC自己的 
HttpMessageConverter还是定义在RequestMappingHandlerAdapter里,并且被MethodArgumentResolverHandlerMethodReturnValueHandler解析数据的时候所用到。

  1. public RequestMappingHandlerAdapter() {
  2. StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
  3. stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
  4.  
  5. this.messageConverters = new ArrayList<HttpMessageConverter<?>>(4);
  6. this.messageConverters.add(new ByteArrayHttpMessageConverter());
  7. this.messageConverters.add(stringHttpMessageConverter);
  8. this.messageConverters.add(new SourceHttpMessageConverter<Source>());
  9. this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
  10. }

这里面四个HttpMessageConverter,重点介绍下AllEncompassingFormHttpMessageConverter,看源码。
  1. public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {
  2.  
  3. private static final boolean jaxb2Present =
  4. ClassUtils.isPresent("javax.xml.bind.Binder", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
  5.  
  6. private static final boolean jackson2Present =
  7. ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
  8. ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
  9.  
  10. private static final boolean jackson2XmlPresent =
  11. ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
  12.  
  13. private static final boolean gsonPresent =
  14. ClassUtils.isPresent("com.google.gson.Gson", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
  15.  
  16.  
  17. public AllEncompassingFormHttpMessageConverter() {
  18. addPartConverter(new SourceHttpMessageConverter<Source>());
  19.  
  20. if (jaxb2Present && !jackson2XmlPresent) {
  21. addPartConverter(new Jaxb2RootElementHttpMessageConverter());
  22. }
  23.  
  24. if (jackson2Present) {
  25. addPartConverter(new MappingJackson2HttpMessageConverter());
  26. } else if (gsonPresent) {
  27. addPartConverter(new GsonHttpMessageConverter());
  28. }
  29.  
  30. if (jackson2XmlPresent) {
  31. addPartConverter(new MappingJackson2XmlHttpMessageConverter());
  32. }
  33. }
  34.  
  35. }

它的主要作用是,从类加载器去查找相关的类,只有这些转换器需要的类(jar包被你引入了)存在,那么转换器才会被加载进去,比如我们在项目中引入了

  1. <dependency>
  2. <groupId>com.fasterxml.jackson.core</groupId>
  3. <artifactId>jackson-databind</artifactId>
  4. <version>${jackson.version}</version>
  5. </dependency>

这样就预示着MappingJackson2HttpMessageConverter被加入到了SpringMVC中,后面在使用@RequestBody@ResponseBodyRequestResponseBodyMethodProcessor起作用的时候,它就会寻找能够匹配的Converter,进而找到MappingJackson2HttpMessageConverter用来转换对象。

那如果我们引入了

  1. <dependency>
  2. <groupId>com.fasterxml.jackson.dataformat</groupId>
  3. <artifactId>jackson-dataformat-xml</artifactId>
  4. <version>${jackson.version}</version>
  5. </dependency>

那也就预示着MappingJackson2XmlHttpMessageConverter被加入转换器队列中了,那么你使用@ResponseBody,响应类型为Content-Type: application/xml;charset=UTF-8,估计是默认请求头Accept直接为application/xml,具体源码我就不去翻了。

这里写图片描述

当用户发送请求后,@RequestBody注解会读取请求body中的数据,通过获取请求头Header中的Content-Type来确认请求头的数据格式,从而来为请求数据适配合适的转换器。例如contentType:applicatin/json,那么转换器会适配MappingJackson2HttpMessageConverter。响应时候的时候同理,@ResponseBody注解会通过检测请求头HeaderAccept属性来适配对应响应的转换器。



这里写图片描述

我这里因为没找到合适的例子去实现自己的HttpMessageConverter,但想到了另外一个可以拿来用的例子。

  1. @RequestMapping(value="simple")
  2. public String simple(){
  3. return "abc中国";
  4. }

这个方法我们访问的时候,应该是SpringMVC会去找一个abc中国.jsp的页面,由于找不到,会报404

但当我们稍微修改如下:

  1. @RequestMapping(value="simple")
  2. @ResponseBody
  3. public String simple(){
  4. return "abc中国";
  5. }

由于我们使用了@ResponseBody,当我们再次发起请求的时候,请求头Header中Accept为 
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

根据上个小节所介绍到的,它应该会找到StringHttpMessageConverter,并完成转换,输出的直接是返回值,然而我们却发现结果中文乱码了,这是因为该类默认支持的编码格式是ISO-8859-1

  1. package org.springframework.http.converter;
  2.  
  3. import java.io.IOException;
  4. import java.io.UnsupportedEncodingException;
  5. import java.nio.charset.Charset;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8.  
  9. import org.springframework.http.HttpInputMessage;
  10. import org.springframework.http.HttpOutputMessage;
  11. import org.springframework.http.MediaType;
  12. import org.springframework.util.StreamUtils;
  13.  
  14.  
  15. public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
  16.  
  17. public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");

那我们应当如何使之支持UTF-8呢,这也是接下来我们所需要介绍的,应当如何覆盖SpringMVC自己的转换器,或者新增自定义的转换器。

  1. <mvc:annotation-driven>
  2. <mvc:message-converters>
  3. <bean class="org.springframework.http.converter.StringHttpMessageConverter">
  4. <property name="supportedMediaTypes">
  5. <list>
  6. <value>text/plain;charset=UTF-8</value>
  7. <value>text/html;charset=UTF-8</value>
  8. </list>
  9. </property>
  10. </bean>
  11. </mvc:message-converters>
  12. </mvc:annotation-driven>

如果我们不想使用系统默认的转换器,还可以去掉系统给你定义的,然后重新定义你所需要的转换器,如下:

  1. <mvc:annotation-driven>
  2. <mvc:message-converters register-defaults="false">
  3. <bean class="org.springframework.http.converter.StringHttpMessageConverter">
  4. <property name="supportedMediaTypes">
  5. <list>
  6. <value>text/plain;charset=UTF-8</value>
  7. <value>text/html;charset=UTF-8</value>
  8. </list>
  9. </property>
  10. </bean>
  11. </mvc:message-converters>
  12. </mvc:annotation-driven>

我个人的意见是,如果没有特别的需要,不要重新定义自己的转换器,我们大可覆盖或者新增就行了。



原创粉丝点击