為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

點擊上方 "程序員小樂"關注, 星標或置頂一起成長

第一時間與你相約


每日英文

Be alike flower. Spread beauty and happiness wherever you stay; irrespective of your surroundings.

像花兒一樣,無論身在何處,不管周遭環境如何,都依然瀟灑的綻放自己的美麗,活出自己的精彩。


每日掏心話

人生嚐盡了百味方覺甜美。偶爾隱匿的陽光,再來時更溫暖;撲朔迷離的希望,出現時才令人驚喜。

來自:format丶 | 責編:樂樂

鏈接:cnblogs.com/fangjian0423/p/springMVC-xml-json-convert.html

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

程序員小樂(ID:study_tech)第 830 次推文 圖片來自百度


往日回顧:史上首次!個人所得稅退稅來了!如何退?怎麼操作?


正文

前言


SpringMVC是目前主流的Web MVC框架之一。

現象


本文使用的demo基於maven,是根據入門blog的例子繼續寫下去的。
我們先來看一看對應的現象。我們這裡的配置文件 *-dispatcher.xml中的關鍵配置如下(其他常規的配置文件不在講解,可參考本文一開始提到的入門blog):

(視圖配置省略)





  • <resources><annotation-driven><component-scan>



  • pom中需要有以下依賴(Spring依賴及其他依賴不顯示):











  • <dependency> <groupid>org.codehaus.jackson/<groupid> <artifactid>jackson-core-asl/<artifactid> <version>1.9.13/<version>/<dependency><dependency> <groupid>org.codehaus.jackson/<groupid> <artifactid>jackson-mapper-asl/<artifactid> <version>1.9.13/<version>/<dependency>




這個依賴是json序列化的依賴。


ok。我們在Controller中添加一個method:








  • @RequestMapping("/xmlOrJson")@ResponseBodypublic Map<string> xmlOrJson() { Map<string> map = new HashMap<string>(); map.put("list", employeeService.list()); return map;}/<string>/<string>/<string>




直接訪問地址:


為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

我們看到,短短几行配置。使用@ResponseBody註解之後,Controller返回的對象 自動被轉換成對應的json數據,在這裡不得不感嘆SpringMVC的強大。


我們好像也沒看到具體的配置,唯一看到的就是*-dispatcher.xml中的一句配置:<annotation-driven>。其實就是這個配置,導致了java對象自動轉換成json對象的現象。


那麼spring到底是如何實現java對象到json對象的自動轉換的呢?為什麼轉換成了json數據,如果想轉換成xml數據,那該怎麼辦?

源碼分析


本文使用的spring版本是4.0.2。

在講解<annotation-driven>這個配置之前,我們先了解下Spring的消息轉換機制。@ResponseBody這個註解就是使用消息轉換機制,最終通過json的轉換器轉換成json數據的。


HttpMessageConverter接口就是Spring提供的http消息轉換接口。有關這方面的知識大家可以參考"參考資料"中的第二條鏈接,裡面講的很清楚。


為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


下面開始分析<annotation-driven>這句配置:


這句代碼在spring中的解析類是:


為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


在AnnotationDrivenBeanDefinitionParser源碼的152行parse方法中:

分別實例化了RequestMappingHandlerMapping,ConfigurableWebBindingInitializer,RequestMappingHandlerAdapter等諸多類。


其中RequestMappingHandlerMapping和RequestMappingHandlerAdapter這兩個類比較重要。


RequestMappingHandlerMapping處理請求映射的,處理@RequestMapping跟請求地址之間的關係。


RequestMappingHandlerAdapter是請求處理的適配器,也就是請求之後處理具體邏輯的執行,關係到哪個類的哪個方法以及轉換器等工作,這個類是我們講的重點,其中它的屬性messageConverters是本文要講的重點。

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


私有方法:getMessageConverters

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


從代碼中我們可以,RequestMappingHandlerAdapter設置messageConverters的邏輯:


1.如果<annotation-driven>節點有子節點message-converters,那麼它的轉換器屬性messageConverters也由這些子節點組成。/<annotation-driven>


message-converters的子節點配置如下:








  • <annotation-driven> <message-converters> <bean> <bean> /<message-converters>/<annotation-driven>




2.message-converters子節點不存在或它的屬性register-defaults為true的話,加入其他的轉換器:ByteArrayHttpMessageConverter、StringHttpMessageConverter、ResourceHttpMessageConverter等。


我們看到這麼一段:

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


這些boolean屬性是哪裡來的呢,它們是AnnotationDrivenBeanDefinitionParser的靜態變量。

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


其中ClassUtils中的isPresent方法如下:

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

看到這裡,讀者應該明白了為什麼本文一開始在pom文件中需要加入對應的jackson依賴,為了讓json轉換器jackson成為默認轉換器之一。


<annotation-driven>的作用讀者也明白了。/<annotation-driven>

下面我們看如何通過消息轉換器將java對象進行轉換的。

RequestMappingHandlerAdapter在進行handle的時候,會委託給HandlerMethod(具體由子類ServletInvocableHandlerMethod處理)的invokeAndHandle方法進行處理,這個方法又轉接給HandlerMethodReturnValueHandlerComposite處理。


HandlerMethodReturnValueHandlerComposite維護了一個HandlerMethodReturnValueHandler列表。


HandlerMethodReturnValueHandler是一個對返回值進行處理的策略接口,這個接口非常重要。然後找到對應的HandlerMethodReturnValueHandler對結果值進行處理。


最終找到RequestResponseBodyMethodProcessor這個Handler(由於使用了@ResponseBody註解)。


RequestResponseBodyMethodProcessor的supportsReturnType方法:

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


然後使用handleReturnValue方法進行處理:

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

我們看到,這裡使用了轉換器。  


具體的轉換方法:

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

至於為何是請求頭部的Accept數據,讀者可以進去debug這個getAcceptableMediaTypes方法看看。我就不羅嗦了~~~

ok。至此,我們走遍了所有的流程。

現在,回過頭來看。為什麼一開始的demo輸出了json數據?

我們來分析吧。

由於我們只配置了<annotation-driven>,因此使用spring默認的那些轉換器。/<annotation-driven>

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

很明顯,我們看到了2個xml和1個json轉換器。 要看能不能轉換,得看HttpMessageConverter接口的public boolean canWrite(Class> clazz, MediaType mediaType)方法是否返回true來決定的。


我們先分析SourceHttpMessageConverter:

它的canWrite方法被父類AbstractHttpMessageConverter重寫了。

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


發現SUPPORTED_CLASSES中沒有Map類(本文demo返回的是Map類),因此不支持。


下面看Jaxb2RootElementHttpMessageConverter:


這個類直接重寫了canWrite方法。

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

需要有XmlRootElement註解。很明顯,Map類當然沒有。


最終MappingJackson2HttpMessageConverter匹配,進行json轉換。(為何匹配,請讀者自行查看源碼)

實例講解


我們分析了轉換器的轉換過程之後,下面就通過實例來驗證我們的結論吧。


首先,我們先把xml轉換器實現。


之前已經分析,默認的轉換器中是支持xml的。下面我們加上註解試試吧。

由於Map是jdk源碼中的部分,因此我們用Employee來做demo。


因此,Controller加上一個方法:






  • @RequestMapping("/xmlOrJsonSimple")@ResponseBodypublic Employee xmlOrJsonSimple() { return employeeService.getById(1);}



  • 實體中加上@XmlRootElement註解


為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


結果如下:

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


我們發現,解析成了xml。


這裡為什麼解析成xml,而不解析成json呢?

之前分析過,消息轉換器是根據class和mediaType決定的。


我們使用firebug看到:

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

我們發現Accept有xml,沒有json。因此解析成xml了。

我們再來驗證,同一地址,HTTP頭部不同Accept。看是否正確。










  • $.ajax({ url: "${request.contextPath}/employee/xmlOrJsonSimple", success: function(res) { console.log(res); }, headers: { "Accept": "application/xml" }});




為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?











  • $.ajax({ url: "${request.contextPath}/employee/xmlOrJsonSimple", success: function(res) { console.log(res); }, headers: { "Accept": "application/json" }});



為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

驗證成功。

關於配置


如果不想使用<annotation-driven>中默認的RequestMappingHandlerAdapter的話,我們可以在重新定義這個bean,spring會覆蓋掉默認的RequestMappingHandlerAdapter。










  • <bean> <property> <list> <bean> <bean> <bean> /<list> /<property>/<bean>




或者如果只想換messageConverters的話。








  • <annotation-driven> <message-converters> <bean> <bean> /<message-converters>/<annotation-driven>



如果還想用其他converters的話。

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

以上是spring-mvc jar包中的converters。


這裡我們使用轉換xml的MarshallingHttpMessageConverter。


這個converter裡面使用了marshaller進行轉換

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


我們這裡使用XStreamMarshaller。  

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?

為什麼Spring MVC能自動轉換json xml,你研究過它背後的原理嗎?


json沒有轉換器,返回406.


至於xml格式的問題,大家自行解決吧。這裡用的是XStream~。


使用這種方式,pom別忘記了加入xstream的依賴:






  • <dependency> <groupid>com.thoughtworks.xstream/<groupid> <artifactid>xstream/<artifactid> <version>1.4.7/<version>/<dependency>



總結


寫了這麼多,可能讀者覺得有點羅嗦。畢竟這也是自己的一些心得,希望都能說出來與讀者共享。


剛接觸SpringMVC的時候,發現這種自動轉換機制很牛逼,但是一直沒有研究它的原理,目前,算是了了一個小小心願吧,SpringMVC還有很多內容,以後自己研究其他內容的時候還會與大家一起共享的。


文章難免會出現一些錯誤,希望讀者們能指明出來。


  • oschina.net/HeliosFly/blog/205343

  • my.oschina.net/lichhao/blog/172562

  • docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html


歡迎在留言區留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發,學習能力的提升上有新的認識,歡迎轉發分享給更多人。


猜你還想看


阿里、騰訊、百度、華為、京東最新面試題彙集

手把手教你 Netty 實現自定義協議!

一個簡單的例子帶你理解HashMap

【整理】Spring 常用註解!千萬不要錯過!

關注訂閱號「程序員小樂」,收看更多精彩內容
嘿,你在看嗎?



分享到:


相關文章: