转自:http://cnge06.iteye.com/blog/1755856
突然对ruby on rails来了兴趣,便捣弄了两天,用脚手架做了个hello world,实质上也就是一行命令的事,的确够效率。看一下Controller,其REST风格实在太简洁了。
- # GET /posts
- # GET /posts.json
- def index
- @posts = Post.all
- respond_to do |format|
- format.html # index.html.erb
- format.json { render json: @posts }
- end
- end
一直在关注着spring,如今一般的web项目,从前到后几乎可以用spring直接搞定了。因此,对spring mvc及REST相关内容自然也有所了解。
于是今天就在想,spring mvc应该也可以对同一资源,实现多种展现才是,前提当然是在同一个方法里。(之前是为json的处理单独写一个Controller的method的,倒也没什么问题。)
就如以下:
- @RequestMapping(value={"/user/{id}","/user/{id}.json"})
- public ModelAndView show(@PathVariable(value="id")String id){
- User user=userService.get(Long.valueOf(id));
- ModelAndView mav=new ModelAndView("show");
- mav.addObject(user);
- return mav;
- }
当我请求/user/1时,便会回应show的视图,当我请求/user/{id}.json时,便会回应json格式的数据。
而spring mvc中ContentNegotiatingViewResolver便能满足这个需求,除了json,还有xml/rss。
相关资讯可查看这里(里面还对RESTful中同一资源多种表述进行描述的三种方式进行了讨论。)。
我当前使用spring版本是3.2,在这个版本里,对ContentNegotiatingViewResolver作了修改,原样参照以上链接配置,出现java.lang.ClassCastException: java.lang.String cannot be cast to org.springframework.http.MediaType 的异常。版本原因造成这个问题。
在springsource的论坛里有人询问和解答,请看这里或者这里。
起作用的配置,例:
- <!-- REST -->
- <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
- <property name="order" value="1" />
- <property name="contentNegotiationManager">
- <bean class="org.springframework.web.accept.ContentNegotiationManager">
- <constructor-arg>
- <bean class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy">
- <constructor-arg>
- <map>
- <entry key="json" value="application/json"/>
- <entry key="xml" value="application/xml"/>
- </map>
- </constructor-arg>
- </bean>
- </constructor-arg>
- </bean>
- </property>
- <property name="defaultViews">
- <list>
- <!-- JSON View -->
- <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
- <!-- XML View -->
- <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
- <constructor-arg>
- <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
- <property name="packagesToScan">
- <list>
- <value>documentLoader.domain</value>
- </list>
- </property>
- </bean>
- </constructor-arg>
- </bean>
- </list>
- </property>
- </bean>
- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
- <property name="prefix" value="/WEB-INF/views/" />
- <property name="suffix" value=".jsp" />
- </bean>
转自:http://www.blogjava.net/badqiu/archive/2009/12/21/306793.html
一.REST内容协商介绍
RESTful服务中很重要的一个特性即是同一资源,多种表述.也即如下面描述的三种方式:
1.使用http request header: Accept





2.使用扩展名



3.使用参数
/user/123?format=json //将返回json数据
而以上三种各有优缺点:
1.使用Accept header:
这一种为教科书中通常描述的一种,理想中这种方式也是最好的,但如果你的资源要给用户直接通过浏览器访问(即html展现),那么由于浏览器的差异,发送上来的Accept Header头将是不一样的. 将导致服务器不知要返回什么格式的数据给你. 下面是浏览器的Accept Header








2.使用扩展名
丧失了同一url多种展现的方式,但现在这种在实际环境中是使用最多的.因为更加符合程序员的审美观.
3.使用参数
可能由于要编写的字符较多,所以较少使用.
带着上面的选择: 使用扩展名,我们来看一下spring中如何配置这部分.
二.spring rest配置
现spring完成内容协商(content negotiation)的工作是由ContentNegotiatingViewResolver来完成的.它的工作模式支持我上面讲的三种,
ContentNegotiatingViewResolver是根据客户提交的MimeType(如 text/html,application/xml)来跟服务端的一组viewResover的MimeType相比较,如果符合,即返回viewResover的数据.
而 /user/123.xml, ContentNegotiatingViewResolver会首先将 .xml 根据mediaTypes属性将其转换成 application/xml,然后完成前面所说的比较.
下面是ContentNegotiatingViewResolver的完全配置.
<!-- 根据客户端的不同的请求决定不同的view进行响应, 如 /blog/1.json /blog/1.xml -->
< bean class ="org.springframework.web.servlet.view.ContentNegotiatingViewResolver" >
<!-- 设置为true以忽略对Accept Header的支持 -->
< property name ="ignoreAcceptHeader" value ="true" />
<!-- 在没有扩展名时即: "/user/1" 时的默认展现形式 -->
< property name ="defaultContentType" value ="text/html" />
<!-- 扩展名至mimeType的映射,即 /user.json => application/json -->
< property name ="mediaTypes" >
< map >
< entry key ="json" value ="application/json" />
< entry key ="xml" value ="application/xml" />
</ map >
</ property >
<!-- 用于开启 /userinfo/123?format=json 的支持 -->
< property name ="favorParameter" value ="false" />
< property name ="viewResolvers" >
< list >
< bean class ="org.springframework.web.servlet.view.BeanNameViewResolver" />
< bean class ="org.springframework.web.servlet.view.InternalResourceViewResolver" >
< property name ="viewClass" value ="org.springframework.web.servlet.view.JstlView" />
< property name ="prefix" value ="/pages" />
< property name ="suffix" value =".jsp" ></ property >
</ bean >
</ list >
</ property >
< property name ="defaultViews" >
< list >
<!-- for application/json -->
< bean class ="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
<!-- for application/xml -->
<!--
<bean class="org.springframework.web.servlet.view.xml.MarshallingView" >
<property name="marshaller">
<bean class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</property>
</bean>
-->
</ list >
</ property >
</ bean >
Spring MVC 之 ContentNegotiatingViewResolver(ajax+json格式的输入和输出)
Spring mvc处理json需要使用jackson的类库,因此为支持json格式的输入输出需要先修改pom.xml增加jackson包的引用
- <!-- json -->
- <dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-core-lgpl</artifactId>
- <version>1.8.1</version>
- </dependency>
- <dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-mapper-lgpl</artifactId>
- <version>1.8.1</version>
- </dependency>
先修改之前的helloworld.jsp,增加客户端json格式的数据输入。
- var cfg = {
- type: 'POST',
- data: JSON.stringify({userName:'winzip',password:'password',mobileNO:'13818881888'}),
- dataType: 'json',
- contentType:'application/json;charset=UTF-8',
- success: function(result) {
- alert(result.success);
- }
- };
- function doTestJson(actionName){
- cfg.url = actionName;
- $.ajax(cfg);
- }
根据前面的分析,在spring mvc中解析输入为json格式的数据有两种方式
1:使用@RequestBody来设置输入
- @RequestMapping("/json1")
- @ResponseBody
- public JsonResult testJson1(@RequestBody User u){
- log.info("get json input from request body annotation");
- log.info(u.getUserName());
- return new JsonResult(true,"return ok");
- }
2:使用HttpEntity来实现输入绑定
- @RequestMapping("/json2")
- public ResponseEntity<JsonResult> testJson2(HttpEntity<User> u){
- log.info("get json input from HttpEntity annotation");
- log.info(u.getBody().getUserName());
- ResponseEntity<JsonResult> responseResult = new ResponseEntity<JsonResult>( new JsonResult(true,"return ok"),HttpStatus.OK);
- return responseResult;
- }
Json格式的输出也对应有两种方式
1:使用@responseBody来设置输出内容为context body
2:返回值设置为ResponseEntity<?>类型,以返回context body
另外,第三种方式是使用ContentNegotiatingViewResolver来设置输出为json格式,需要修改servlet context配置文件如下
- <bean
- class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
- <property name="order" value="1" />
- <property name="mediaTypes">
- <map>
- <entry key="json" value="application/json" />
- </map>
- </property>
- <property name="defaultViews">
- <list>
- <bean
- class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
- </list>
- </property>
- <property name="ignoreAcceptHeader" value="true" />
- </bean>
但这种格式的输出会返回{model类名:{内容}} 的json格式,
例如,以下代码
- @RequestMapping("/json3.json")
- public JsonResult testJson3(@RequestBody User u){
- log.info("handle json output from ContentNegotiatingViewResolver");
- return new JsonResult(true,"return ok");
- }
期望的返回是 {success:true,message:”return ok”};
但实际返回的却是 {"jsonResult":{"success":true,"msg":"return ok"}}
原因是MappingJacksonJsonView中对返回值的处理未考虑modelMap中只有一个值的情况,直接是按照mapName:{mapResult}的格式来返回数据的。
修改方法,重载MappingJacksonJsonView类并重写filterModel方法如下
- protected Object filterModel(Map<String, Object> model) {
- Map<?, ?> result = (Map<?, ?>) super.filterModel(model);
- if (result.size() == 1) {
- return result.values().iterator().next();
- } else {
- return result;
- }
- }
对应的ContentNegotiatingViewResolver修改如下
- <bean
- class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
- <property name="order" value="1" />
- <property name="mediaTypes">
- <map>
- <entry key="json" value="application/json" />
- </map>
- </property>
- <property name="defaultViews">
- <list>
- <bean
- class="net.zhepu.json.MappingJacksonJsonView" />
- </list>
- </property>
- <property name="ignoreAcceptHeader" value="true" />
- </bean>