Spring Framework 3.2增强了ContentNegotiationManager,使得配置多视图变得尤为轻松。并且对于多视图的解析的实现都可以有多种供你选择。如果你想使用Spring作为网站后台,并且想完全分离 前后台的代码依赖,那么了解如何配置Spring的基于内容协商多视图是非常必须而且有用的。下面就来看看如何配置Spring,让它支持JSON/XML视图吧。
先看看Spring的官方文档关于Content Negotiation的improvements
可见,内容协商其实说白了很简单,就是根据请求规则决定返回什么样的内容类型。后缀规则、参数规则、Accept头规则、固定的内容类型等。注意,这里只是决定,不是具体提供内容类型的地方。
好了,现在正式开始配置的介绍:
这里默认你已经配置好Spring的DispatcherServlet,并设置映射路径是“/”,例如下面的配置:
lab-order
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
上下文配置地址
contextConfigLocation
/WEB-INF/mvc-servlet.xml
log4j配置位置
log4jConfigLocation
/WEB-INF/log4j.properties
核心Servlet
mvc
org.springframework.web.servlet.DispatcherServlet
mvc
/
log4j配置侦听
org.springframework.web.util.Log4jConfigListener
Spring上下文侦听加载器
org.springframework.web.context.ContextLoaderListener
编码过滤器
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
encodingFilter
/*
然后配置核心Sevlet的上下文环境,这里是文件“mvc-servlet.xml”。如下:
“mvc-servlet.xml”中
content-negotiation-manager属性就是指定内容协商管理器的bean,按照官方文档是这样的配置,但是有一个问题,这个问题稍后再和大家讨论。contentNegotiationManager这个bean配置在“view-resolve.xml中”:
java.util.Collection
org.laohu.modules.school.model.School
org.laohu.modules.school.model.Laboratory
org.laohu.modules.school.model.stuff.LabMgr
application/json
application/xml
这里配置了多个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:
application/json
application/json
即将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的实现,有兴趣的同学可以去看看,我还没来得及细看这些实现,所以不会多讲这方面内容。