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处理异常解析
参考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.参考
文中难免有不足之处,烦请指正