1. 前言
SpringMVC 是目前 J2EE 平台的主流 web 框架, 不熟悉的园友可以看 SpringMVC 源码阅读入门, 它交代了 SpringMVC 的基础知识和源码阅读的技巧
本文将通过源码 (基于 Spring4.3.7) 分析, 弄清楚 SpringMVC 如何实现 JSON,xml 的转换
2. 源码分析
测试方法, 浏览器输入 http://localhost:8080/springmvcdemo/employee/xmlOrJson
- @RequestMapping(value="/xmlOrJson",produces={"application/json; charset=UTF-8"})
- @ResponseBody
- public Map<String, Object> xmlOrJson() {
- Map<String, Object> map = new HashMap<String, Object>();
- map.put("list", employeeService.list());
- return map;
- }
Demo 点击这里获取, 根据 SpringMVC 源码阅读: Controller 中参数解析我们知道, RequestResponseBodyMethodProcessor 支持 JSON 类型数据的转换, 我们上回遇到了消息转换器 MessageConverter, 我没有解释它是什么, 这篇文章我们将会揭开它的面纱
那么, 我们就从 RequestResponseBodyMethodProcessor 开始进行分析, 在 handleReturnValue 方法 169 行打断点, 当有 @ResponseBody 注解时会进入
170 行获取请求路径, 请求信息
171 行获取 Content-Type, 响应信息
打开 writeWithMessageConverters 方法, 进入 AbstractMessageConverterMethodProcessor 类
167 行声明 outputValue 用来接收 Controller 返回值
168 行声明 valueType 接收返回对象类型
183 行 requestMediaTypes 获取 Accept-Type
184 行 producibleMediaTypes 获取 Content-Type, 正是我们在 @RequestMapping 中配置的 produces
190 行声明 compatibleMediaTypes 的 Set 来获取匹配的 MediaTypes, 那么它是如何匹配到 "application/json" 的呢?
191~197 行对 requestMediaTypes 和 producibleMediaTypes 循环遍历, 进行匹配, 得到 compatibleMediaTypes
我们看看 requestMediaTypes
第一到第三个都不是 "application/json", 第四个使用了终极大招,"*/*" 表示所有类型, 所以 producibleMediaTypes 总有类型能与 requestMediaTypes 匹配上
继续分析 writeWithMessageConverters 方法
221 行获取选中的 MediaType
222 行遍历 HttpMessageConverter
223 行判断当前 HttpMessageConverter 是不是 GenericHttpMessageConverter 类型
GenericHttpMessageConverter 是一个接口, 它的实现类如下
根据官网资料, 我们知道各种 HttpMessageConverter 的作用, 而 MappingJackson2HttpMessageConverter 是我们需要的, 用以解析 JSON
我们需要 Jackson2.x jar 包来支持 MappingJackson2HttpMessageConverter
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <version>2.6.5</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-core</artifactId>
- <version>2.6.5</version>
- </dependency>
224 行检验当前 GenericHttpMessageConverter 是否可以被 Converter 写入
现在我们要弄清楚, HttpMessageConverter 从哪里来, 我们点击 AbstractMessageConverterMethodProcessor 类 191 行 this.messageConverters 跳转到了 AbstractMessageConverterMethodArgumentResolver,AbstractMessageConverterMethodArgumentResolver 是 AbstractMessageConverterMethodProcessor 的父类, messageConverters 是 AbstractMessageConverterMethodArgumentResolver 的属性, ctrl+f 搜索, 我们找到了 AbstractMessageConverterMethodArgumentResolver 的构造方法初始化了 HttpMessageConverter
HttpMessageConverter 如下
来自于我们在 dispatcher-servlet.xml 自定义的 RequestMappingHandlerAdapter
- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
- <property name="messageConverters">
- <list>
- <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
- <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
- <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
- <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
- <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
- </list>
- </property>
- </bean>
messageConverters 是 RequestMappingHandlerAdapter 的一个 list 属性, 在 RequestMappingHandlerAdapter 我们配置了五种 HttpMessageConverter, 包装成 list, 并注入到 Spring
RequestMappingHandler 构造方法给我们加入了默认的 HttpMessageConverter, 在 setMessaageConverters 会被我们自定义 messageConverters 覆盖
this.messageConverters 是构造方法加入, messageConverters 是我们传入的参数, set 方法后于构造方法执行, 故覆盖之
再回到 AbstractMessageConverterMethodProcessor 类 writeWithMessageConverters 方法, 看下 224 行 canWrite 做了什么
对 canWrite ctrl+alt+b, 根据父类继承关系, 我们锁定 AbstractGenericHttpMessageConverter
继续点击 canWrite 方法
在 AbstractGenericHttpMessageConverter 的父类 AbstractHttpMessageConverter 里给出了具体实现
根据官网我们知道 MappingJackson2HttpMessageConverter 负责转换 JSON, 有必要看下该类的 canWrite 方法
打断点我发现, 确实进入了该类的 canWrite 方法, 但是并没有做什么事, 真正的逻辑在它的父类 AbstractHttpMessageConverter 处理, 刚才我们已经分析过
JSON 部分我已经分析完毕, 我现在来分析下解析 xml, 分析步骤和 JSON 一致, 除了解析类不一样
根据官网我们知道, Jaxb2RootElementHttpMessageConverter 和 MappingJackson2XMLHttpMessageConverter 可以转换 xml
我们先来看看 Jaxb2RootElementHttpMessageConverter 的 canWrite 方法
显然, 想使用 Jaxb2RootElementHttpMessageConverter 解析 xml 需要 @XmlRootElement 的支持
我们再来看看 MappingJackson2XMLHttpMessageConverter, 该类在 Spring4.1 版本引入, 实现了 HttpMessageConverter, 需要 Jackson2.6 以上的版本支持
MappingJackson2XMLHttpMessageConverter 在初始化会进入其方法
50 行 MappingJackson2XMLHttpMessageConverter 无参构造函数负责 build ObjectMapper, 实质上是 build 了 XmlMapper(ObjectMapper 子类)
60 行 MappingJackson2XMLHttpMessageConverter 有参构造函数继承父类 AbstractJackson2HttpMessageConverter 构造函数, 实例化支持 xml 的 MediaType
63 行判断 ObjectMapper 是否是 XmlMapper
MappingJackson2XMLHttpMessageConverter 类继承图如下
我奇怪地发现, MappingJackson2XMLHttpMessageConverter 为什么没有 canWrite 方法, 原来它直接用父类 AbstractGenericHttpMessageConverter 的 canWrite,AbstractGenericHttpMessageConverter 再调用自身的父类 AbstractHttpMessageConverter 的 canWrite, 和我刚才分析 JSON 解析逻辑是一致的
XmlMapper 类可以读取和写入 xml, 是一个工具类, 我就不叙述了
最后再说下 dispatcher-servlet.xml 中 < mvc:annotation-driven/>是个什么东西
查阅官方文档,<mvc:annotation-driven/>自动帮我们注册了
- RequestMappingHandlerMapping
- RequestMappingHandlerAdapter
- ExceptionHandlerExceptionResolver
RequestMappingHandlerMapping 处理请求映射
RequestMappingHandlerAdapter 处理参数和返回值
ExceptionHandlerExceptionResolver 处理异常解析
参考, MVC 的前缀由 MvcNamespaceHandler 解析
AnnotationDrivenBeanDefinitionParser 负责解析 annotation-driven 注解, AnnotationDrivenBeanDefinitionParser 实现了 BeanDefinitionParser, 我们重点看下 parse 方法
188 行定义 RequestMappingHandlerMapping 的 Bean
228 行定义 RequestMappingHandlerAdapter 的 Bean
281 行定义 ExceptionHandlerExceptionResolver 的 Bean
312 行注册 RequestMappingHandlerMapping
313 行注册 RequestMappingHandlerAdapter
315 行注册 ExceptionHandlerExceptionResolver
3. 实例
3.1 测试 MappingJackson2HttpMessageConverter 解析 JSON
前文说过
- @RequestMapping(value="/returnJson",produces={"application/json; charset=UTF-8"})
- @ResponseBody
- public Map<String, Object> xmlOrJson() {
- Map<String, Object> map = new HashMap<String, Object>();
- map.put("list", employeeService.list());
- return map;
- }
3.2 测试 Jaxb2RootElementHttpMessageConverter 解析 xml
使用 Jaxb2RootElementHttpMessageConverter 除了使用自定义 RequestMappingHandlerAdapter, 也可以使用 < mvc:annotation-driven/>, 它会为你自动注入 Jaxb2RootElementHttpMessageConverter
我注释掉我在 dispatcher-servlet.xml 自定义的 RequestMappingHandlerAdapter
在 RequestMappingHandlerAdapter 的 afterPropertiesSet 方法打断点, 可以看到, 有 AllEncompassingFormHttpMessageConverter
AllEncompassingFormHttpMessageConverter 为我们加入了 Jaxb2RootElementHttpMessageConverter
在 Employee 实体类中加入注解
- @Entity
- @Table(name="t_employee")
- @XmlRootElement
- @XmlAccessorType(XmlAccessType.NONE)
- public class Employee {
- @XmlElement
- private Integer id;
- @XmlElement
- private String name;
- @XmlElement
- private Integer age;
- @XmlElement
- private Dept dept;
- @GeneratedValue
- @Id
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getAge() {
- return age;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- @ManyToOne
- public Dept getDept() {
- return dept;
- }
- public void setDept(Dept dept) {
- this.dept = dept;
- }
- }
我这里封装了一个 xml 解析类, 用来规范 xml 输出格式
- @XmlRootElement(name = "xml")
- @XmlAccessorType(XmlAccessType.NONE)
- public class XmlActionResult<T> extends BaseXmlResult{
- @XmlElements({
- @XmlElement(name="employee",type = Employee.class)
- })
- private T data;
- public String getCode() {
- return code;
- }
- public void setCode(String code) {
- this.code = code;
- }
- public String getMessage() {
- return message;
- }
- public void setMessage(String message) {
- this.message = message;
- }
- public T getData() {
- return data;
- }
- public void setData(T data) {
- this.data = data;
- }
- }
测试方法:
- @RequestMapping(value="/testCustomObj", produces={"application/xml; charset=UTF-8"},method = RequestMethod.GET)
- @ResponseBody
- public XmlActionResult<Employee> testCustomObj(@RequestParam(value = "id") int id,
- @RequestParam(value = "name") String name) {
- XmlActionResult<Employee> actionResult = new XmlActionResult<Employee>();
- Employee e = new Employee();
- e.setId(id);
- e.setName(name);
- e.setAge(20);
- e.setDept(new Dept(2,"部门"));
- actionResult.setCode("200");
- actionResult.setMessage("Success with XML");
- actionResult.setData(e);
- return actionResult;
- }
返回结果如下
和预期一致
3.3 测试 MappingJackson2XMLHttpMessageConverter 解析 xml
demo 来自于 Arvind Rai, 我在百度没有搜到合适的使用 MappingJackson2XMLHttpMessageConverter 的 demo, 大部分网友使用 Jaxb2RootElementHttpMessageConverter, 遂 Google 了下.
所需的 jar 包
- <dependency>
- <groupId>com.fasterxml.jackson.dataformat</groupId>
- <artifactId>jackson-dataformat-xml</artifactId>
- <version>2.8.7</version>
- </dependency>
在 dispatcher-servlet.xml 自定义 RequestMappingHandlerAdapter 的 messageConverters 加入 MappingJackson2XMLHttpMessageConverter
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"/>
新建一个实体类, 使用 @JacksonXmlRootElement, 用法和 @XmlRootElement 类似
- @JacksonXmlRootElement(localName="company-info", namespace="com.concretepage")
- public class Company {
- @JacksonXmlProperty(localName="id", isAttribute=true)
- private Integer id;
- @JacksonXmlProperty(localName="company-name")
- private String companyName;
- @JacksonXmlProperty(localName="ceo-name")
- private String ceoName;
- @JacksonXmlProperty(localName="no-emp")
- private Integer noEmp;
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getCompanyName() {
- return companyName;
- }
- public void setCompanyName(String companyName) {
- this.companyName = companyName;
- }
- public String getCeoName() {
- return ceoName;
- }
- public void setCeoName(String ceoName) {
- this.ceoName = ceoName;
- }
- public Integer getNoEmp() {
- return noEmp;
- }
- public void setNoEmp(Integer noEmp) {
- this.noEmp = noEmp;
- }
- }
测试方法
- @RequestMapping(value= "/fetch/{id}", produces = MediaType.APPLICATION_XML_VALUE)
- @ResponseBody
- public Company getForObjectXMLDemo(@PathVariable(value = "id") Integer id) {
- Company comp = new Company();
- comp.setId(id);
- comp.setCompanyName("XYZ");
- comp.setCeoName("ABCD");
- comp.setNoEmp(100);
- return comp;
- }
运行结果如下
符合预期
4. 总结
- <mvc:annotation-driven>
- 使 spring 为我们配置默认的 MessageConverter
- <mvc:annotation-driven>
- 的解析类在 BeanDefinitionParser, 实现类为 AnnotationDrivenBeanDefinitionParser,getMessageConverters
- 方法获取 MessageConverter,parse 方法解析元素
AbstractMessageConverterMethodArgumentResolver 的构造方法初始化了 HttpMessageConverter
RequestMappingHandler 加入了 HttpMessageConverter
AbstractHttpMessageConverter 的 canWrite 方法判断是否支持 MediaType
如果解析 xml 用 Jaxb2RootElementHttpMessageConverter 类, Jaxb2RootElementHttpMessageConverter 的 canWrite 会判断是否有注解支持
AbstractMessageConverterMethodProcessor 类 writeWithMessageConverters 方法根据 MediaType 选取合适的 HttpMessageConverter 解析数据成 xml/JSON 数据
RequestResponseBodyMethodProcessor 的 handleReturnValue 处理返回值
5. 参考
文中难免有不足之处, 烦请指正
- https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-responsebody
- https://blog.csdn.net/lqzkcx3/article/details/78159708
来源: https://www.cnblogs.com/Java-Starter/p/10342909.html