SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html
本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何實現Json,Xml的轉換git
測試方法,瀏覽器輸入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/>自動幫咱們註冊了
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
前文說過
@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; }
使用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; }
返回結果以下
和預期一致
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; }
運行結果以下
符合預期
<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處理返回值
文中不免有不足之處,煩請指正