分析Spring MVC自定义消息转换器

时间:2024-03-15 18:47:49

分析

在Spring MVC的请求处理阶段大致可以分为以下几个步骤:根据请求url找到对应的Controller的Method,参数绑定后通过反射执行方法,将执行结果交给视图解析器响应视图。

而在这之前需要通过消息转化器。Spring Boot底层通过HttpMessageConverters依靠JacksonJava实体类输出为JSON格式。当有多个转换器可用时,根据消息对象类型和需要的内容类型选择最适合的转换器使用:

分析Spring MVC自定义消息转换器

         有时候根据实际需求默认的消息转换器不符合我们的需求,这个时候就需要定制消息转换器了。先看看开启Web MVC的配置支持的@EnableWebMvc

         分析Spring MVC自定义消息转换器

它引入了DelegatingWebMvcConfiguration这个类,而这个类又继承自WebMvcConfigurationSupport:

分析Spring MVC自定义消息转换器

在WebMvcConfigurationSupport中定义了很多静态常量,通过判断相关的类是否存在来适配相应的MediaType:

分析Spring MVC自定义消息转换器

分析Spring MVC自定义消息转换器

这里有我们常用的解析JSON的ObjectMapper,还有一个解析XML的XmlMapper项目中是没有的:

分析Spring MVC自定义消息转换器

现在将这个类相关的依赖引入工程:

分析Spring MVC自定义消息转换器

重新启动项目,再访问。

使用谷歌浏览器访问,发现响应数据格式为XML,是支持中文的:

分析Spring MVC自定义消息转换器

从调试信息可以看出现在响应头的Content-Type为application/xhtml+xml;charset=UTF-8,是符合Accept的:

分析Spring MVC自定义消息转换器

也就是说此时默认响应Content-Type为application/xhtml+xml;charset=UTF-8(很明显是因为application/xhtml+xml权重较高)。但是使用浏览器容易被浏览器默认的Accept所干扰,改用其他Web服务测试插件

而如果在请求的时候指定Accept为application/json;charset=UTF-8,再来测试一下:

分析Spring MVC自定义消息转换器

此时响应Content-Type为application/json;charset=UTF-8,不再是之前的application/xhtml+xml;charset=UTF-8

分析Spring MVC自定义消息转换器

如果不指定Accept:

分析Spring MVC自定义消息转换器

响应结果:

分析Spring MVC自定义消息转换器

从上面的测试可以看出消息转换器会根据Accept响应(能响应的,而这个能不能与相应的处理类是否存在有关)最合适的消息。在WebMvcConfigurationSupport中使用一个有序List集合来管理转换器,并有优先级,而且JSON格式的优先级是比XML高的:

分析Spring MVC自定义消息转换器

可以看出默认会有一些消息转换器,然后会根据上面介绍的静态常量判断是否添加相应的消息转换器,通过调试可以看出最终List<HttpMessageConverter<?>>的顺序如下:

分析Spring MVC自定义消息转换器

这里都是HttpMessageConverter的实现,如MappingJackson2HttpMessageConverter:

分析Spring MVC自定义消息转换器

可以看出响应数据格式为JSON底层也就是使用ObjectMapper进行序列化的:

分析Spring MVC自定义消息转换器

这里调用了父类的方法:

分析Spring MVC自定义消息转换器

实际调用方法为:

分析Spring MVC自定义消息转换器

从这个方法可以看出设置默认编码为DEFAULT_CHARSET,即:

 

分析Spring MVC自定义消息转换器

也就能够解释之前默认的Content-Type中含有charset=UTF-8了。

在父类AbstractJackson2HttpMessageConverter中有两个方法,分别控制http请求数据的读和写:

分析Spring MVC自定义消息转换器

Spring MVC底层首先会进行精确匹配,如无法精确匹配通过轮询遍历的方式选择权重最高的类型进行读/写:

分析Spring MVC自定义消息转换器

分析Spring MVC自定义消息转换器

这样也就解释了为什么引入XmlMapper后使用谷歌浏览器会返回XML格式。而使用Web测试插件不指定Accept会返回JSON格式。

自定义消息转换器

在上面介绍的DelegatingWebMvcConfiguration类中:

分析Spring MVC自定义消息转换器

在上面介绍的WebMvcConfigurationSupport中的addDefaultHttpMessageConverters()方法是在getMessageConverters()中被调用的,而这个方法也是为一个成员属性赋值:

分析Spring MVC自定义消息转换器

分析Spring MVC自定义消息转换器

这里首先会执行configureMessageConverters()方法,入参为messageConverters,目前是个空集合:

分析Spring MVC自定义消息转换器

执行完成后,如果messageConverters为空,就会执行addDefaultHttpMessageConverters()方法,如果不为空,也就是我们定义了,就不会执行addDefaultHttpMessageConverters()方法。最终都会执行extendMessageConverters()方法:

分析Spring MVC自定义消息转换器

同时getMessageConverters()返回的List<HttpMessageConverter<?>>也是会被Spring MVC的核心RequestMappingHandlerAdapter组件设值:

分析Spring MVC自定义消息转换器

可以先写个测试类试一试:

分析Spring MVC自定义消息转换器

分析Spring MVC自定义消息转换器

在之前的分析中,响应JSON格式是优先于XML格式的,这里可以将MappingJackson2XmlHttpMessageConverter设置为优先级最高,再进行测试:

分析Spring MVC自定义消息转换器

分析Spring MVC自定义消息转换器

此时响应结果为XML格式,也进一步证实的之前的结论的正确性:

分析Spring MVC自定义消息转换器

下面来扩展自定义消息,首先定义一个序列化工具,为了简单起见还是使用Jackson序列化JSON,但是会使用自己定义的媒体数据类型(application/mymessage)。

通过前面的分析,想要自定义消息转换器就需要将自定义的转换器放入List<HttpMessageConverter<?>>集合中去,然后有消息过来根据媒体数据类型进行匹配。所以自定义转换器必须为HttpMessageConverter接口的实现,这里继承它的一个抽象子类AbstractGenericHttpMessageConverter:

分析Spring MVC自定义消息转换器

将自定义的MyHttpMessageConverter放到List<HttpMessageConverter<?>>中去(这里也可以实现extendMessageConverters()方法):

分析Spring MVC自定义消息转换器

测试Controller(produces和consumes均为自定义的媒体数据类型):

分析Spring MVC自定义消息转换器

分析Spring MVC自定义消息转换器

测试发现失败了:

分析Spring MVC自定义消息转换器

错误信息显示的为argument type mismatch\nHandlerMethod details: \nController。也就是说是在处理HandlerMethod的时候出现了问题,根据4.2中的流程图可以发现,请求是先进入消息转换器再进入Spring MVC的,也就是说是在read后进行参数绑定的时候出错了,最后发现是这里写错了:

分析Spring MVC自定义消息转换器

这里不能写成Object.class,因为我这里是使用的Jackson序列化数据,如果是Object.class转换后的对象是Map无法与User匹配。

更改后进行测试,测试成功:

分析Spring MVC自定义消息转换器