Spring MVC不仅支持各种网页视图,也支持JSON、XML这样的视图。而且还支持内容协商,也就是根据传入的扩展名、请求参数、Accept Header等信息决定具体采用哪种视图。我们先来看看Spring的JSON和XML视图。
手动实现JSON或XML视图
这是最笨的办法,不过描述起来很简单。我们只要按照自己习惯的方式使用自己熟悉的类库,在控制器中手动将要转换的对象转化成JSON或XML字符串,然后返回给@ResponseBody方法即可。这种方法的缺点是Spring不知道我们具体返回的类型,所以我们需要自己设置响应的Contet-Type和编码。
常用的JSON序列化库有Jackson、谷歌的Gson和阿里的FastJason等,可以根据需求选择合适的。Java有很多XML序列化库,也可以直接使用Spring封装的OXM功能(详见Spring文档)。
Spring的多视图支持
除了手动进行对象的转换之外,我们还可以利用Spring提供的多视图功能。这也是本文主要讲的内容。
Spring的JSON视图支持
Jackson
Spring提供了对Jackson序列化库的支持,如果使用Gradle的话,在项目中添加如下一行,Gradle会自动引入Jackson和其依赖的几个包。
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.6'
如果Spring发现类路径上有Jackson库存在,就会自动注册一个MappingJackson2HttpMessageConverter
。这意味着我们直接在@ResponseBody方法中返回要转换的对象即可,Spring会使用MappingJackson2HttpMessageConverter
来转换。
@RequestMapping("/users")
@ResponseBody
public List<User> users() {
return users;
}
我们如果使用相应的URL来访问,会得到类似下面的输出。
[{"name":"yitian","age":24,"gender":"男"},{"name":"zhang3","age":23,"gender":"男"},{"name":"li4","age":24,"gender":"男"},{"name":"meimei","age":22,"gender":"女"}]
当然也可以对生成的Json进行定制,请参阅Jackson文档。
FastJson
另外我又研究了一下,Jackson类库默认不能进行JDK8新日期时间API的转换,需要额外引入几个扩展,配置起来略麻烦。而且现在阿里FastJson的速度应该是最快的。所以我们也来学习一下FastJson。
首先添加FastJson的依赖。
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.24'
由于Spring没有默认的FastJson支持,所以我们没办法向Jackson那样让Spring自动注册。不过阿里针对Spring框架也编写了相应的支持类。我们只要向Spring注册一个FastJsonHttpMessageConverter4
即可。如果你使用Spring 4.2以下,那么使用FastJsonHttpMessageConverter
类;如果使用Spring 4.2以上,使用带4的那个。
<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的消息转换器没有指定Content-Type,所以如果我们直接使用的话会收到text/html类型的消息。解决办法就是在消息转换器中设置Content-Type。这样设置以后, 我们直接返回对象的话,FastJson就会将对象转换为JSON字符串了。
Spring的XML视图支持
JAXB
Spring提供了OXM,可以将Java对象映射为XML文件。这里我们先说一说XML序列化库JAXB。自JDK6开始,自带了JAXB的实现。因此我们不需要额外引入类库了。JAXB的缺点是当我们使用注解配置OXM的时候必须注解每个要映射的类。因此如果我们需要返回一个用户集合List<User>
,我们就必须定义一个Users
类,它包含一个List<User>
实例。这里用到的User类也进行了相应字段的注解。
@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会检查类路径是否包含JAXB的实现。如果包含的话会自动注册一个Jaxb2RootElementHttpMessageConverter
,所以当我们在@ResponseBody
方法中返回相应的对象。Spring就会自动将它转换为XML。
@RequestMapping("/users")
@ResponseBody
public Users users() {
Users us = new Users();
us.setUsers(users);
return us;
}
Jackson XML
另外如果Spring检测到类路径上存在jackson-dataformat-xml
,就会自动注册一个MappingJackson2XmlHttpMessageConverter
。这样返回的对象就会使用Jackson的XML映射功能转换为XML。
XStream
XStream是一个优秀的XML序列化框架,默认情况下无需配置即可使用,而且要配置也很简单,添加一些aliases
即可。缺点就是可以反序列化匿名对象,可能有安全问题,所以我们一般需要使用supportedClasses
控制它可以反序列化的类。
首先先来添加XStream的依赖项。
compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.9'
Spring没有命名空间来简化XStream配置。所以我们只能手动声明一个XStream实例。
<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>
然后将它配置到消息转换器中。
<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>
这样,当我们的方法返回一组User对象时,就可以得到正确的XML输出了。
<users>
<user>
<name>yitian</name>
<age>24</age>
<gender>男</gender>
</user>
...
</users>
内容协作
所谓内容写作,指的是Spring可以根据请求的扩展名、查询参数或者Accept头等信息,决定使用哪种视图展示数据。常用的做法就是为一系列数据指定JSON、XML等不同的数据展示方式。在前面讨论了这么多视图的实现方式之后。我们终于可以来研究一下内容协作了。
默认情况下的内容协定
首先来看看这个方法。假如我们引入了Jackson和Jackson XML的依赖,那么这个方法到底会返回什么样的数据呢?Spring文档 内容协作这一节已经说了,Spring默认会注册json
, xml
,rss
, atom
这四种类型的内容协定,如果相应的依赖存在的话。Spring会先查找文件扩展名,根据扩展名来返回相应的视图;如果扩展名不存在,就会根据Accept头来判断。所以如果我们访问/users.json
,就会返回JSON视图,如果访问/users.xml
,就会返回XML视图。
@RequestMapping("/users")
@ResponseBody
public List<User> users() {
return users;
}
自定义内容协定
上面的Jackson和Jackson XML都是Spring默认自动注册的转换器。如果我们使用其他的转换器,或者希望自己指定内容协定的策略,就需要自定义内容协定了。内容协定需要两个类来支持:内容协定视图解析器用来指定要使用的视图;内容协定管理器用于配置内容协定的策略。
内容协定视图解析器
内容协定视图解析器需要配置一个默认视图和一系列视图解析器。它会根据媒体类型(也就是Content-Type)来查找合适的视图解析器。如果没有视图解析器满足需要的媒体类型,就会使用默认视图来渲染。
下面是一个配置内容协定视图解析器的例子。由于我们使用@ResponseBody直接向响应输出结果并通过消息转换器转换。所以我们这里其实不需要配置内容协定视图解析器。
<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>
内容协商管理器
内容协商管理器用于指定内容协商的策略。我们在Spring中声明一个ContentNegotiationManagerFactoryBean
,然后设置它的属性即可。最后将它的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>
内容协商管理器可定义的东西有很多。这里简单说明一下:
- mediaType。指定可接受的媒体类型,需要一些键值对,值为实际的媒体类型。
- useJaf。指定是否使用JavaBeans(TM) Activation Framework。这个类库可以自动检测扩展名为实际媒体类型。如果不指定我们就可以使用自己的设置。
- ignoreAcceptHeader。指定是否忽略Accept头的类型。
- favorPathExtension。指定是否使用路径扩展名判断媒体类型。
- favorParameter。指定是否使用参数判断媒体类型。
- parameterName。指定参数的名称。
这些属性通过合理配置,就可以得到我们想要的功能了。如果指定了路径扩展名,那么访问/users.xml
会返回XML,访问/users.json
会返回JSON;如果指定了Accept头,那么当Accept头包含application/json
会返回JSON,XML也是类似;如果指定了请求参数,那么当访问/users?type=xml
时返回XML,JSON类似。由于一般内容协定常用于Rest程序,所以最常用的还是通过路径扩展名和Accept头来判断媒体类型。