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.參考
文中難免有不足之處,煩請指正