Spring MVC不僅支持各種網(wǎng)頁視圖,也支持JSON、XML這樣的視圖。而且還支持內(nèi)容協(xié)商,也就是根據(jù)傳入的擴(kuò)展名、請(qǐng)求參數(shù)、Accept Header等信息決定具體采用哪種視圖。我們先來看看Spring的JSON和XML視圖。
手動(dòng)實(shí)現(xiàn)JSON或XML視圖
這是最笨的辦法,不過描述起來很簡單。我們只要按照自己習(xí)慣的方式使用自己熟悉的類庫,在控制器中手動(dòng)將要轉(zhuǎn)換的對(duì)象轉(zhuǎn)化成JSON或XML字符串,然后返回給@ResponseBody方法即可。這種方法的缺點(diǎn)是Spring不知道我們具體返回的類型,所以我們需要自己設(shè)置響應(yīng)的Contet-Type和編碼。
常用的JSON序列化庫有Jackson、谷歌的Gson和阿里的FastJason等,可以根據(jù)需求選擇合適的。Java有很多XML序列化庫,也可以直接使用Spring封裝的OXM功能(詳見Spring文檔)。
Spring的多視圖支持
除了手動(dòng)進(jìn)行對(duì)象的轉(zhuǎn)換之外,我們還可以利用Spring提供的多視圖功能。這也是本文主要講的內(nèi)容。
Spring的JSON視圖支持
Jackson
Spring提供了對(duì)Jackson序列化庫的支持,如果使用Gradle的話,在項(xiàng)目中添加如下一行,Gradle會(huì)自動(dòng)引入Jackson和其依賴的幾個(gè)包。
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.6'
如果Spring發(fā)現(xiàn)類路徑上有Jackson庫存在,就會(huì)自動(dòng)注冊(cè)一個(gè)MappingJackson2HttpMessageConverter。這意味著我們直接在@ResponseBody方法中返回要轉(zhuǎn)換的對(duì)象即可,Spring會(huì)使用MappingJackson2HttpMessageConverter來轉(zhuǎn)換。
@RequestMapping("/users")
@ResponseBody
public List<User> users() {
return users;
}
我們?nèi)绻褂孟鄳?yīng)的URL來訪問,會(huì)得到類似下面的輸出。
[{"name":"yitian","age":24,"gender":"男"},{"name":"zhang3","age":23,"gender":"男"},{"name":"li4","age":24,"gender":"男"},{"name":"meimei","age":22,"gender":"女"}]
當(dāng)然也可以對(duì)生成的Json進(jìn)行定制,請(qǐng)參閱Jackson文檔。
FastJson
另外我又研究了一下,Jackson類庫默認(rèn)不能進(jìn)行JDK8新日期時(shí)間API的轉(zhuǎn)換,需要額外引入幾個(gè)擴(kuò)展,配置起來略麻煩。而且現(xiàn)在阿里FastJson的速度應(yīng)該是最快的。所以我們也來學(xué)習(xí)一下FastJson。
首先添加FastJson的依賴。
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.24'
由于Spring沒有默認(rèn)的FastJson支持,所以我們沒辦法向Jackson那樣讓Spring自動(dòng)注冊(cè)。不過阿里針對(duì)Spring框架也編寫了相應(yīng)的支持類。我們只要向Spring注冊(cè)一個(gè)FastJsonHttpMessageConverter4即可。如果你使用Spring 4.2以下,那么使用FastJsonHttpMessageConverter類;如果使用Spring 4.2以上,使用帶4的那個(gè)。
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4">
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
另外,新版本的FastJson的消息轉(zhuǎn)換器沒有指定Content-Type,所以如果我們直接使用的話會(huì)收到text/html類型的消息。解決辦法就是在消息轉(zhuǎn)換器中設(shè)置Content-Type。這樣設(shè)置以后, 我們直接返回對(duì)象的話,F(xiàn)astJson就會(huì)將對(duì)象轉(zhuǎn)換為JSON字符串了。
Spring的XML視圖支持
JAXB
Spring提供了OXM,可以將Java對(duì)象映射為XML文件。這里我們先說一說XML序列化庫JAXB。自JDK6開始,自帶了JAXB的實(shí)現(xiàn)。因此我們不需要額外引入類庫了。JAXB的缺點(diǎn)是當(dāng)我們使用注解配置OXM的時(shí)候必須注解每個(gè)要映射的類。因此如果我們需要返回一個(gè)用戶集合List<User>,我們就必須定義一個(gè)Users類,它包含一個(gè)List<User>實(shí)例。這里用到的User類也進(jìn)行了相應(yīng)字段的注解。
@XmlRootElement
public class Users {
private List<User> users;
public List<User> getUsers() {
return users;
}
@XmlElement
public void setUsers(List<User> users) {
this.users = users;
}
}
和前面的Jackson支持一樣,Spring會(huì)檢查類路徑是否包含JAXB的實(shí)現(xiàn)。如果包含的話會(huì)自動(dòng)注冊(cè)一個(gè)Jaxb2RootElementHttpMessageConverter,所以當(dāng)我們?cè)?code>@ResponseBody方法中返回相應(yīng)的對(duì)象。Spring就會(huì)自動(dòng)將它轉(zhuǎn)換為XML。
@RequestMapping("/users")
@ResponseBody
public Users users() {
Users us = new Users();
us.setUsers(users);
return us;
}
Jackson XML
另外如果Spring檢測到類路徑上存在jackson-dataformat-xml,就會(huì)自動(dòng)注冊(cè)一個(gè)MappingJackson2XmlHttpMessageConverter。這樣返回的對(duì)象就會(huì)使用Jackson的XML映射功能轉(zhuǎn)換為XML。
XStream
XStream是一個(gè)優(yōu)秀的XML序列化框架,默認(rèn)情況下無需配置即可使用,而且要配置也很簡單,添加一些aliases即可。缺點(diǎn)就是可以反序列化匿名對(duì)象,可能有安全問題,所以我們一般需要使用supportedClasses控制它可以反序列化的類。
首先先來添加XStream的依賴項(xiàng)。
compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.9'
Spring沒有命名空間來簡化XStream配置。所以我們只能手動(dòng)聲明一個(gè)XStream實(shí)例。
<bean id="xStreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="supportedClasses">
<list>
<value>yitian.learn.entity.User</value>
<value>java.util.List</value>
</list>
</property>
<property name="aliases">
<props>
<prop key="users">java.util.List</prop>
<prop key="user">yitian.learn.entity.User</prop>
</props>
</property>
</bean>
然后將它配置到消息轉(zhuǎn)換器中。
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="xStreamMarshaller"/>
<property name="unmarshaller" ref="xStreamMarshaller"/>
</bean>
</mvc:message-converters>
這樣,當(dāng)我們的方法返回一組User對(duì)象時(shí),就可以得到正確的XML輸出了。
<users>
<user>
<name>yitian</name>
<age>24</age>
<gender>男</gender>
</user>
...
</users>
內(nèi)容協(xié)作
所謂內(nèi)容寫作,指的是Spring可以根據(jù)請(qǐng)求的擴(kuò)展名、查詢參數(shù)或者Accept頭等信息,決定使用哪種視圖展示數(shù)據(jù)。常用的做法就是為一系列數(shù)據(jù)指定JSON、XML等不同的數(shù)據(jù)展示方式。在前面討論了這么多視圖的實(shí)現(xiàn)方式之后。我們終于可以來研究一下內(nèi)容協(xié)作了。
默認(rèn)情況下的內(nèi)容協(xié)定
首先來看看這個(gè)方法。假如我們引入了Jackson和Jackson XML的依賴,那么這個(gè)方法到底會(huì)返回什么樣的數(shù)據(jù)呢?Spring文檔 內(nèi)容協(xié)作這一節(jié)已經(jīng)說了,Spring默認(rèn)會(huì)注冊(cè)json, xml,rss, atom這四種類型的內(nèi)容協(xié)定,如果相應(yīng)的依賴存在的話。Spring會(huì)先查找文件擴(kuò)展名,根據(jù)擴(kuò)展名來返回相應(yīng)的視圖;如果擴(kuò)展名不存在,就會(huì)根據(jù)Accept頭來判斷。所以如果我們?cè)L問/users.json,就會(huì)返回JSON視圖,如果訪問/users.xml,就會(huì)返回XML視圖。
@RequestMapping("/users")
@ResponseBody
public List<User> users() {
return users;
}
自定義內(nèi)容協(xié)定
上面的Jackson和Jackson XML都是Spring默認(rèn)自動(dòng)注冊(cè)的轉(zhuǎn)換器。如果我們使用其他的轉(zhuǎn)換器,或者希望自己指定內(nèi)容協(xié)定的策略,就需要自定義內(nèi)容協(xié)定了。內(nèi)容協(xié)定需要兩個(gè)類來支持:內(nèi)容協(xié)定視圖解析器用來指定要使用的視圖;內(nèi)容協(xié)定管理器用于配置內(nèi)容協(xié)定的策略。
內(nèi)容協(xié)定視圖解析器
內(nèi)容協(xié)定視圖解析器需要配置一個(gè)默認(rèn)視圖和一系列視圖解析器。它會(huì)根據(jù)媒體類型(也就是Content-Type)來查找合適的視圖解析器。如果沒有視圖解析器滿足需要的媒體類型,就會(huì)使用默認(rèn)視圖來渲染。
下面是一個(gè)配置內(nèi)容協(xié)定視圖解析器的例子。由于我們使用@ResponseBody直接向響應(yīng)輸出結(jié)果并通過消息轉(zhuǎn)換器轉(zhuǎn)換。所以我們這里其實(shí)不需要配置內(nèi)容協(xié)定視圖解析器。
<bean id="contentNegotiatingViewResolver"
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="contentNegotiationManager" ref="contentNegotiationManager"/>
<property name="defaultViews">
<list>
<bean id="jsonView"
class="com.alibaba.fastjson.support.spring.FastJsonJsonView"/>
<bean id="xmlView"
class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller" ref="xStreamMarshaller"/>
</bean>
</list>
</property>
<property name="viewResolvers">
<list>
<bean id="internalResourceViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
</bean>
內(nèi)容協(xié)商管理器
內(nèi)容協(xié)商管理器用于指定內(nèi)容協(xié)商的策略。我們?cè)赟pring中聲明一個(gè)ContentNegotiationManagerFactoryBean,然后設(shè)置它的屬性即可。最后將它的id傳給mvc:annotation-driven的content-negotiation-manager屬性即可。
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json"/>
<entry key="xml" value="application/xml"/>
</map>
</property>
<property name="useJaf" value="true"/>
<property name="ignoreAcceptHeader" value="false"/>
<property name="favorPathExtension" value="true"/>
<property name="favorParameter" value="false"/>
<property name="parameterName" value="type"/>
</bean>
內(nèi)容協(xié)商管理器可定義的東西有很多。這里簡單說明一下:
- mediaType。指定可接受的媒體類型,需要一些鍵值對(duì),值為實(shí)際的媒體類型。
- useJaf。指定是否使用JavaBeans(TM) Activation Framework。這個(gè)類庫可以自動(dòng)檢測擴(kuò)展名為實(shí)際媒體類型。如果不指定我們就可以使用自己的設(shè)置。
- ignoreAcceptHeader。指定是否忽略Accept頭的類型。
- favorPathExtension。指定是否使用路徑擴(kuò)展名判斷媒體類型。
- favorParameter。指定是否使用參數(shù)判斷媒體類型。
- parameterName。指定參數(shù)的名稱。
這些屬性通過合理配置,就可以得到我們想要的功能了。如果指定了路徑擴(kuò)展名,那么訪問/users.xml會(huì)返回XML,訪問/users.json會(huì)返回JSON;如果指定了Accept頭,那么當(dāng)Accept頭包含application/json會(huì)返回JSON,XML也是類似;如果指定了請(qǐng)求參數(shù),那么當(dāng)訪問/users?type=xml時(shí)返回XML,JSON類似。由于一般內(nèi)容協(xié)定常用于Rest程序,所以最常用的還是通過路徑擴(kuò)展名和Accept頭來判斷媒體類型。