Spring Framework 3.2增强了ContentNegotiationManager,使得配置多视图变得尤为轻松。并且对于多视图的解析的实现都可以有多种供你选择。如果你想使用Spring作为网站后台,并且想完全分离 前后台的代码依赖,那么了解如何配置Spring的基于内容协商多视图是非常必须而且有用的。下面就来看看如何配置Spring,让它支持JSON/XML视图吧。
先看看Spring的官方文档关于Content Negotiation的improvements
可见,内容协商其实说白了很简单,就是根据请求规则决定返回什么样的内容类型。后缀规则、参数规则、Accept头规则、固定的内容类型等。注意,这里只是决定,不是具体提供内容类型的地方。
好了,现在正式开始配置的介绍:
这里默认你已经配置好Spring的DispatcherServlet,并设置映射路径是“/”,例如下面的配置:
-
<?xml version="1.0" encoding="UTF-8"?>
-
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
-
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
-
id="WebApp_ID" version="3.0">
-
<display-name>lab-order</display-name>
-
<welcome-file-list>
-
<welcome-file>index.html</welcome-file>
-
<welcome-file>index.htm</welcome-file>
-
<welcome-file>index.jsp</welcome-file>
-
<welcome-file>default.html</welcome-file>
-
<welcome-file>default.htm</welcome-file>
-
<welcome-file>default.jsp</welcome-file>
-
</welcome-file-list>
-
-
<context-param>
-
<description>上下文配置地址</description>
-
<param-name>contextConfigLocation</param-name>
-
<param-value>/WEB-INF/mvc-servlet.xml</param-value>
-
</context-param>
-
-
<context-param>
-
<description>log4j配置位置</description>
-
<param-name>log4jConfigLocation</param-name>
-
<param-value>/WEB-INF/log4j.properties</param-value>
-
</context-param>
-
-
<servlet>
-
<description>核心Servlet</description>
-
<servlet-name>mvc</servlet-name>
-
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
-
</servlet>
-
<servlet-mapping>
-
<servlet-name>mvc</servlet-name>
-
<url-pattern>/</url-pattern>
-
</servlet-mapping>
-
-
<listener>
-
<description>log4j配置侦听</description>
-
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
-
</listener>
-
-
<listener>
-
<description>Spring上下文侦听加载器</description>
-
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
-
</listener>
-
-
<filter>
-
<description>编码过滤器</description>
-
<filter-name>encodingFilter</filter-name>
-
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
-
<init-param>
-
<param-name>encoding</param-name>
-
<param-value>UTF-8</param-value>
-
</init-param>
-
<init-param>
-
<param-name>forceEncoding</param-name>
-
<param-value>true</param-value>
-
</init-param>
-
</filter>
-
<filter-mapping>
-
<filter-name>encodingFilter</filter-name>
-
<url-pattern>/*</url-pattern>
-
</filter-mapping>
-
-
</web-app>
然后配置核心Sevlet的上下文环境,这里是文件“mvc-servlet.xml”。如下:
-
<beans xmlns="http://www.springframework.org/schema/beans"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
-
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
-
xmlns:c="http://www.springframework.org/schema/c"
-
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
-
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
-
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
-
-
<!-- 开启注解驱动 -->
-
<!-- 这样可以使用@Controller这些注解 -->
-
<mvc:annotation-driven
-
content-negotiation-manager="contentNegotiationManager"></mvc:annotation-driven>
-
<!-- 开启默认处理 -->
-
<!-- 这样静态资源就可以访问了 -->
-
<mvc:default-servlet-handler />
-
-
<!-- 开启上下文注解配置 -->
-
<!-- 使@Autowire注解被支持 -->
-
<context:annotation-config></context:annotation-config>
-
<!-- 配置注解扫描包 -->
-
<context:component-scan base-package="org.laohu.modules"
-
annotation-config="true"></context:component-scan>
-
-
<!-- 导入hibernate配置 -->
-
<import resource="hibernate-beans.xml" />
-
<!-- 导入视图解析配置 -->
-
<import resource="view-resolve.xml" />
-
-
<!-- 导入模块配置 -->
-
<import resource="school-module.xml" />
-
</beans>
“mvc-servlet.xml”中
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"></mvc:annotation-driven>
content-negotiation-manager属性就是指定内容协商管理器的bean,按照官方文档是这样的配置,但是有一个问题,这个问题稍后再和大家讨论。contentNegotiationManager这个bean配置在“view-resolve.xml中”:
-
<beans xmlns="http://www.springframework.org/schema/beans"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
-
xmlns:p="http://www.springframework.org/schema/p"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
-
<!-- 配置内容协商视图解析 -->
-
<bean id="contentNegotiatingViewResolver"
-
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
-
<!-- 设置内容协商管理器 -->
-
<property name="contentNegotiationManager" ref="contentNegotiationManager"></property>
-
<!-- 设置默认视图 -->
-
<property name="defaultViews">
-
<list>
-
<ref bean="mappingJacksonJsonView" />
-
<ref bean="marshallingView" />
-
</list>
-
</property>
-
<!-- 设置视图解析器 -->
-
<property name="viewResolvers">
-
<list>
-
<ref bean="defalutViewResolver" />
-
</list>
-
</property>
-
</bean>
-
-
<bean id="defalutViewResolver"
-
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
-
<property name="prefix" value="/WEB-INF/view"></property>
-
<property name="suffix" value=".jsp"></property>
-
</bean>
-
-
<!-- JSON视图 -->
-
<bean id="mappingJacksonJsonView"
-
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"></bean>
-
<!-- XML视图 -->
-
<bean id="marshallingView"
-
class="org.springframework.web.servlet.view.xml.MarshallingView">
-
<property name="marshaller">
-
<bean class="org.springframework.oxm.xstream.XStreamMarshaller">
-
<property name="supportedClasses">
-
<list>
-
<value>java.util.Collection</value>
-
<value>org.laohu.modules.school.model.School</value>
-
<value>org.laohu.modules.school.model.Laboratory</value>
-
<value>org.laohu.modules.school.model.stuff.LabMgr</value>
-
</list>
-
</property>
-
<property name="aliases">
-
<map>
-
<entry value="org.laohu.modules.school.model.School" key="school"></entry>
-
<entry value="org.laohu.modules.school.model.Laboratory"
-
key="laboratory"></entry>
-
<entry value="org.laohu.modules.school.model.stuff.LabMgr"
-
key="labmgr"></entry>
-
</map>
-
</property>
-
</bean>
-
</property>
-
</bean>
-
-
<bean id="contentNegotiationManager"
-
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
-
<property name="mediaTypes">
-
<props>
-
<prop key="json">application/json</prop>
-
<prop key="xml">application/xml</prop>
-
</props>
-
</property>
-
</bean>
-
-
</beans>
这里配置了多个bean,先从contentNegotiateManager说起,contentNegotiateManager并不是直接使用ContentNegotiateManager构造的,而是使用其工厂bean生产的,给他配置属性mediaTypes这个属性是告诉contentNegotiateManager将每一个mediaTypes里的entry作为文件名/URL后缀,其内容类型就是entry对应的value值。也就是说,如果请求的url为http://hostname/xxx/xxx/data.json?p1=2332&p2=abc的话,contentNegotiateManager就会认为你请求的内容类型(Content-Type)为application/json,那么它就要将响应的内容类型(Content-Type)设置为application/json;如下图:
为了方便大家理解,这里贴一下火狐的请求头和服务器的响应头:
-
GET /lab-order/school/.json HTTP/1.1
-
Host: localhost:8080
-
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0 FirePHP/0.7.1
-
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
-
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
-
Accept-Encoding: gzip, deflate
-
Cookie: ext-mainnav.west=o%3Acolumns%3Da%253Ao%25253Aid%25253Ds%2525253Aheader-1041; ext-mainnav.east.description=o%3Acollapsed%3Ds%253Atop; ext-mainnav.east=o%3Awidth%3Dn%253A322; ext-stateGrid=o%3Awidth%3Dn%253A650%5Eheight%3Dn%253A350%5Ecolumns%3Da%253Ao%25253Aid%25253Ds%2525253Aheader-1247%255Eo%25253Aid%25253Ds%2525253Aheader-1248%255Eo%25253Aid%25253Ds%2525253Aheader-1249%255Eo%25253Aid%25253Ds%2525253Aheader-1250%255Eo%25253Aid%25253Ds%2525253Aheader-1251%255Eo%25253Aid%25253Ds%2525253Aheader-1252
-
x-insight: activate
-
Connection: keep-alive
-
Cache-Control: max-age=0
-
HTTP/1.1 200 OK
-
Server: Apache-Coyote/1.1
-
Pragma: no-cache
-
Cache-Control: no-cache, no-store, max-age=0
-
Expires: Thu, 01 Jan 1970 00:00:00 GMT
-
Content-Type: application/json;charset=UTF-8
-
Content-Language: zh-CN
-
Transfer-Encoding: chunked
-
Date: Thu, 04 Apr 2013 17:38:00 GMT
可以看到请求头是普通的text/html请求,但服务器相响应的是application/json的内容类型,表明内容协商工作正常,为了进一步测试内容协商管理器是否是按照这个基于后缀的映射内容类型,我们改变映射关系,修改mediaTypes:
-
<prop key="json">application/json</prop>
-
<prop key="xml">application/json</prop>
即将xml也映射到application/json上,再次使用.xml访问:
请求头:
-
GET /lab-order/school/.xml HTTP/1.1
-
Host: localhost:8080
-
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0 FirePHP/0.7.1
-
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
-
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
-
Accept-Encoding: gzip, deflate
-
Cookie: ext-mainnav.west=o%3Acolumns%3Da%253Ao%25253Aid%25253Ds%2525253Aheader-1041; ext-mainnav.east.description=o%3Acollapsed%3Ds%253Atop; ext-mainnav.east=o%3Awidth%3Dn%253A322; ext-stateGrid=o%3Awidth%3Dn%253A650%5Eheight%3Dn%253A350%5Ecolumns%3Da%253Ao%25253Aid%25253Ds%2525253Aheader-1247%255Eo%25253Aid%25253Ds%2525253Aheader-1248%255Eo%25253Aid%25253Ds%2525253Aheader-1249%255Eo%25253Aid%25253Ds%2525253Aheader-1250%255Eo%25253Aid%25253Ds%2525253Aheader-1251%255Eo%25253Aid%25253Ds%2525253Aheader-1252
-
x-insight: activate
-
Connection: keep-alive
响应头:
-
HTTP/1.1 200 OK
-
Server: Apache-Coyote/1.1
-
Pragma: no-cache
-
Cache-Control: no-cache, no-store, max-age=0
-
Expires: Thu, 01 Jan 1970 00:00:00 GMT
-
Content-Type: application/json;charset=UTF-8
-
Content-Language: zh-CN
-
Transfer-Encoding: chunked
-
Date: Thu, 04 Apr 2013 17:50:55 GMT
响应正文:
{"schools":[{"id":1,"name":"江苏科技大学","position":"江苏镇江","content":30,"labMgrs":[],"laboratorys":[]}]}
以上就证明了当内容协商管理器使用后缀策略时的工作规律。
那么现在内容管理器知道了该响应给浏览器的内容类型后,该如何响应该内容类型给浏览器呢?contentNegotiationManager并不负责视图(数据如何呈现,JSON视图/XML视图等等),真正处理呈现的叫ViewResolver,视图解析器OR视图渲染器(姑且这么翻译),例如上面配置的defaultViewResolver就是默认的视图解析器他解析普通的jsp视图,这里不对它进行讨论,在稍后的文章中或许会专门讲一下它。但在ContentNegotiationViewResolver中配置的ViewResolver是在配置的defaultViews都没有匹配的时候才进行交接的。
那我们看看defaultViews都有些什么:mappingJacksonJsonView——传说中的JSON视图、marshallingView——编组视图XML视图。在defaultViews里注册的视图会在ContentNegotiationViewResolver中注册自己支持的内容类型,当contentNegotiationManager决定好响应的内容类型后,ContentNegotiationViewResolver就会根据该内容类型选择一个兼容的View进行渲染输出,当注册的内容类型都不兼容时,会查询viewResolver中的ViewResolver是否支持该请求,如果ViewResolver表示支持该请求,那么就由该ViewResolver负责视图渲染,如果ViewResolver表示不支持该请求,则查询下一个ViewResolver,直至所有的ViewResolver查询完毕。一旦有View对请求内容匹配,就直接渲染输出,不会进行ViewResolver的查询。由于这里配置了defaultViewResolver是InternalResourceViewResolver,它会对所有的请求说yes,所以这里的其他请求类型(非JSON/XML)都会交给它处理。查看Spring的类库,有不少ViewResolver的实现,有兴趣的同学可以去看看,我还没来得及细看这些实现,所以不会多讲这方面内容。