SpringMVC源碼閱讀:Json,Xml自動轉換

1.前言

SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html

本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何實現Json,Xml的轉換git

2.源碼分析

測試方法,瀏覽器輸入http://localhost:8080/springmvcdemo/employee/xmlOrJsongithub

    @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,我沒有解釋它是什麼,這篇文章咱們將會揭開它的面紗web

那麼,咱們就從RequestResponseBodyMethodProcessor開始進行分析,在handleReturnValue方法169行打斷點,當有@ResponseBody註解時會進入spring

170行獲取請求路徑、請求信息json

171行獲取Content-Type、響應信息瀏覽器

打開writeWithMessageConverters方法,進入AbstractMessageConverterMethodProcessor類mvc

167行聲明outputValue用來接收Controller返回值app

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

相關文章
相關標籤/搜索