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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM