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/>自动帮我们注册了

  1. RequestMappingHandlerMapping
  2. RequestMappingHandlerAdapter
  3. ExceptionHandlerExceptionResolver

RequestMappingHandlerMapping处理请求映射

RequestMappingHandlerAdapter处理参数和返回值

ExceptionHandlerExceptionResolver处理异常解析

参考https://blog.csdn.net/lqzkcx3/article/details/78159708,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

SpringMVC源码阅读:Json,Xml自动转换的更多相关文章

  1. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

  2. SpringMVC源码阅读:拦截器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  3. SpringMVC源码阅读:过滤器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  4. SpringMVC源码阅读:属性编辑器、数据绑定

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  5. SpringMVC源码阅读:Controller中参数解析

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  6. SpringMVC源码阅读:核心分发器DispatcherServlet

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将介绍SpringMVC的核 ...

  7. SpringMVC源码阅读:定位Controller

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码分析,弄清楚Spr ...

  8. SpringMVC源码阅读:视图解析器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  9. SpringMVC源码阅读:异常解析器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

随机推荐

  1. go 编译问题

    golang的编译使用命令 go build , go install;除非仅写一个main函数,否则还是准备好目录结构:GOPATH=工程根目录:其下应创建src,pkg,bin目录,bin目录中用 ...

  2. ASP.NET Core使用EF Core操作MySql数据库

    ASP.NET Core操作MySql数据库, 这样整套环境都可以布署在Linux上 使用微软的 Microsoft.EntityFrameworkCore(2.1.4) 和MySql出的 MySql ...

  3. Day 11 作业题

    1.整理装饰器的形成过程,背诵装饰器的固定格式 固定格式 def wrapper(func): def inner(*args, **kwargs): #执行函数前进行的操作 ret = func(* ...

  4. Code Chef GEOCHEAT(凸包+旋转卡壳+随机化)

    题面 传送门 题解 以下记\(S_i=\{1,2,3,...,i\}\) 我们先用凸包+旋转卡壳求出直径的长度,并记直径的两个端点为\(i,j\)(如果有多条直径随机取两个端点) 因为这个序列被\(r ...

  5. innodb 源码分析 --锁

    innodb引擎中的锁分两种 1)针对数据结构, 如链表 互斥锁 读写锁 http://mysqllover.com/?p=425 http://www.cnblogs.com/justfortast ...

  6. 【LeetCode】414. 第三大的数

    给定一个非空数组,返回此数组中第三大的数.如果不存在,则返回数组中最大的数.要求算法时间复杂度必须是O(n). 示例 1: 输入: [3, 2, 1] 输出: 1 解释: 第三大的数是 1. 示例 2 ...

  7. POJ 1147

    #include <iostream> #include <algorithm> #define MAXN 3005 using namespace std; int _m[M ...

  8. 一元运算符 +,可用于将变量转换为数字;如果变量不能转换,它仍然会是一个数字,但值为 NaN (不是一个数字)

    一元运算符,可用于将变量转换为数字: var y = "5"; var x = + y; console.log(typeof y);//string 类型 console.log ...

  9. linux下安装mysql-5.7.20

    1.下载地址 https://downloads.mysql.com/archives/community/ 2.安装步骤 解压: groupadd mysql useradd -r -g mysql ...

  10. Springboot 打jar包分离lib,配置文件正确方式(二)

    Springboot 打jar包分离lib,配置文件正确方式(二) 背景 从<Springboot 打jar包分离lib,配置文件正确方式>中,可以达到把配置文件和依赖第三方的jar包分离 ...