分析
在Spring MVC的请求处理阶段大致可以分为以下几个步骤:根据请求url找到对应的Controller的Method,参数绑定后通过反射执行方法,将执行结果交给视图解析器响应视图。
而在这之前需要通过消息转化器。Spring Boot底层通过HttpMessageConverters依靠Jackson将Java实体类输出为JSON格式。当有多个转换器可用时,根据消息对象类型和需要的内容类型选择最适合的转换器使用:
有时候根据实际需求默认的消息转换器不符合我们的需求,这个时候就需要定制消息转换器了。先看看开启Web MVC的配置支持的@EnableWebMvc:
它引入了DelegatingWebMvcConfiguration这个类,而这个类又继承自WebMvcConfigurationSupport:
在WebMvcConfigurationSupport中定义了很多静态常量,通过判断相关的类是否存在来适配相应的MediaType:
这里有我们常用的解析JSON的ObjectMapper,还有一个解析XML的XmlMapper项目中是没有的:
现在将这个类相关的依赖引入工程:
重新启动项目,再访问。
使用谷歌浏览器访问,发现响应数据格式为XML,是支持中文的:
从调试信息可以看出现在响应头的Content-Type为application/xhtml+xml;charset=UTF-8,是符合Accept的:
也就是说此时默认响应Content-Type为application/xhtml+xml;charset=UTF-8(很明显是因为application/xhtml+xml权重较高)。但是使用浏览器容易被浏览器默认的Accept所干扰,改用其他Web服务测试插件。
而如果在请求的时候指定Accept为application/json;charset=UTF-8,再来测试一下:
此时响应Content-Type为application/json;charset=UTF-8,不再是之前的application/xhtml+xml;charset=UTF-8
如果不指定Accept:
响应结果:
从上面的测试可以看出消息转换器会根据Accept响应(能响应的,而这个能不能与相应的处理类是否存在有关)最合适的消息。在WebMvcConfigurationSupport中使用一个有序List集合来管理转换器,并有优先级,而且JSON格式的优先级是比XML高的:
可以看出默认会有一些消息转换器,然后会根据上面介绍的静态常量判断是否添加相应的消息转换器,通过调试可以看出最终List<HttpMessageConverter<?>>的顺序如下:
这里都是HttpMessageConverter的实现,如MappingJackson2HttpMessageConverter:
可以看出响应数据格式为JSON底层也就是使用ObjectMapper进行序列化的:
这里调用了父类的方法:
实际调用方法为:
从这个方法可以看出设置默认编码为DEFAULT_CHARSET,即:
也就能够解释之前默认的Content-Type中含有charset=UTF-8了。
在父类AbstractJackson2HttpMessageConverter中有两个方法,分别控制http请求数据的读和写:
Spring MVC底层首先会进行精确匹配,如无法精确匹配通过轮询遍历的方式选择权重最高的类型进行读/写:
这样也就解释了为什么引入XmlMapper后使用谷歌浏览器会返回XML格式。而使用Web测试插件不指定Accept会返回JSON格式。
自定义消息转换器
在上面介绍的DelegatingWebMvcConfiguration类中:
在上面介绍的WebMvcConfigurationSupport中的addDefaultHttpMessageConverters()方法是在getMessageConverters()中被调用的,而这个方法也是为一个成员属性赋值:
这里首先会执行configureMessageConverters()方法,入参为messageConverters,目前是个空集合:
执行完成后,如果messageConverters为空,就会执行addDefaultHttpMessageConverters()方法,如果不为空,也就是我们定义了,就不会执行addDefaultHttpMessageConverters()方法。最终都会执行extendMessageConverters()方法:
同时getMessageConverters()返回的List<HttpMessageConverter<?>>也是会被Spring MVC的核心RequestMappingHandlerAdapter组件设值:
可以先写个测试类试一试:
在之前的分析中,响应JSON格式是优先于XML格式的,这里可以将MappingJackson2XmlHttpMessageConverter设置为优先级最高,再进行测试:
此时响应结果为XML格式,也进一步证实的之前的结论的正确性:
下面来扩展自定义消息,首先定义一个序列化工具,为了简单起见还是使用Jackson序列化JSON,但是会使用自己定义的媒体数据类型(application/mymessage)。
通过前面的分析,想要自定义消息转换器就需要将自定义的转换器放入List<HttpMessageConverter<?>>集合中去,然后有消息过来根据媒体数据类型进行匹配。所以自定义转换器必须为HttpMessageConverter接口的实现,这里继承它的一个抽象子类AbstractGenericHttpMessageConverter:
将自定义的MyHttpMessageConverter放到List<HttpMessageConverter<?>>中去(这里也可以实现extendMessageConverters()方法):
测试Controller(produces和consumes均为自定义的媒体数据类型):
测试发现失败了:
错误信息显示的为argument type mismatch\nHandlerMethod details: \nController。也就是说是在处理HandlerMethod的时候出现了问题,根据4.2中的流程图可以发现,请求是先进入消息转换器再进入Spring MVC的,也就是说是在read后进行参数绑定的时候出错了,最后发现是这里写错了:
这里不能写成Object.class,因为我这里是使用的Jackson序列化数据,如果是Object.class转换后的对象是Map无法与User匹配。
更改后进行测试,测试成功: