spring mvc3 + fastjson 转换 REST 参数以及输出

spring 3可以支持Rest风格参数,其内置了jackson框架作为REST的json参数转换成javabean对象,以及bean对象转换成json参数。

下文以spring 3.1.1 + fastjson为例(低于这个版本的不知道能不能行),说明如何使用springmvc构造resuful参数及输出。

不说废话了,直接上代码:Controller类:

@Controller
public class TestCon
{
    @ResponseBody
    @RequestMapping("/test")
    public Object test(@RequestBody
    final TBean tBean)
    {
        System.out.println(tBean);
        final List lists = new ArrayList();
        lists.add("3434");
        lists.add(tBean);
        lists.add(new String[] { "a", "b" });

        return lists;
    }
}

其中:@RequestBody可以使请求中的REST风格的json字符串直接转换成我们想要的参数TBean(当然,低层是使用fastjson等框架转换).

此方法的返回值为一个List对象,经过转换成json输出后,是一个js数组格式的内容。

@RequestBody不可少,表示将方法的返回值作为响应内容。

 

数据bean对象。

public class TBean
{

    private String id;

    //....
}


 

好了,最关键的一步:在springmvc 的配置文件中(XXX-servlet.xml)配置json格式转换方式:



 

 
    
        
            
                
            
        
    

需要注意的是:方式一和方式二,选择一种实现即可。其它方式一是使用了命名空间的引入,需要引入mvc的命名空间,sprng3.0 不支持这种写法。这两种写法不能同时存在,如果同时存在,功能将异常。

核心代码就是这样的。当然不可缺少的要把fastjson框架的jar包引入到工程中。

 

模拟发起请求:

spring mvc3 + fastjson 转换 REST 参数以及输出_第1张图片

其中:{id:'34'}为REST风格的字符串,将被fastjson转换成TBean对象,直接扔给控制器的方法调用。

Content-Type: application/json; charset=utf-8
Accept: application/json
为请求头信息,以上的代码只支持Context-Type为application/json格式的头,如果没有此头或头内容不是application/json,spring将抛出415的http返回码,导致失败:

spring mvc3 + fastjson 转换 REST 参数以及输出_第2张图片

如果配置了log4j打印spring debug级别的日志,

可以看到日志中出现如下信息:

************************************************************未设置Content-Type信息的错误日志********************************************************************************

DEBUG DefaultAnnotationHandlerMapping - Mapping [/test.do] to HandlerExecutionChain with handler [test.controllers.TestCon@c81672] and 1 interceptor
DEBUG AnnotationMethodHandlerExceptionResolver - Resolving exception from handler [test.controllers.TestCon@c81672]: org.springframework.web.HttpMediaTypeNotSupportedException: Cannot extract parameter (TBean tBean): no Content-Type found
DEBUG ResponseStatusExceptionResolver - Resolving exception from handler [test.controllers.TestCon@c81672]: org.springframework.web.HttpMediaTypeNotSupportedException: Cannot extract parameter (TBean tBean): no Content-Type found
DEBUG DefaultHandlerExceptionResolver - Resolving exception from handler [test.controllers.TestCon@c81672]: org.springframework.web.HttpMediaTypeNotSupportedException: Cannot extract parameter (TBean tBean): no Content-Type found
DEBUG DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcher': assuming HandlerAdapter completed request handling

************************************************************设置为非application/json的错误日志*********************************************************************************

DEBUG DefaultAnnotationHandlerMapping - Mapping [/test.do] to HandlerExecutionChain with handler [test.controllers.TestCon@c81672] and 1 interceptor
DEBUG AnnotationMethodHandlerExceptionResolver - Resolving exception from handler [test.controllers.TestCon@c81672]: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/pan' not supported
DEBUG ResponseStatusExceptionResolver - Resolving exception from handler [test.controllers.TestCon@c81672]: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/pan' not supported
DEBUG DefaultHandlerExceptionResolver - Resolving exception from handler [test.controllers.TestCon@c81672]: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/pan' not supported
DEBUG DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcher': assuming HandlerAdapter completed request handling

 

如果content-type输入正确的话,将会看到正确的输出结果:

spring mvc3 + fastjson 转换 REST 参数以及输出_第3张图片

 

 

*******************************************分隔线*****************************************************************************************************************************************

 

如果你不需要要求输入Context-Type即可解析的话,还有另一种方式实现,参考:

(以下内容转自:http://zjumty.iteye.com/blog/1927890)

自定义Spring MVC3的参数映射和返回值映射 + fastjson 

首先说一下场景:在一些富客户端Web应用程序中我们会有比较多的Ajax调用,并且希望与服务器交互的数据需要是复杂的JSON对象。 fastjon是一个非常高效的JSON序列化和反序列化库,我希望我们输入的JSON串能通过fastjson直接反序列化为一个复杂的JavaBean对象,同时我的返回值能够能通过fastjson序列化为JSON串。 

所谓复杂的JavaBean就是,不仅仅只有一层属性,而是属性也是JavaBean的情况, 例如: 

Java代码    收藏代码
  1. public class FooBean {  
  2.     private String name;  
  3.   
  4.     private Long id;  
  5.   
  6.     private Date birthday;  
  7.   
  8.     private List
     addresses;  
  9.   
  10.     public String getName() {  
  11.         return name;  
  12.     }  
  13.   
  14.     public void setName(String name) {  
  15.         this.name = name;  
  16.     }  
  17.   
  18.     public Long getId() {  
  19.         return id;  
  20.     }  
  21.   
  22.     public void setId(Long id) {  
  23.         this.id = id;  
  24.     }  
  25.   
  26.     public Date getBirthday() {  
  27.         return birthday;  
  28.     }  
  29.   
  30.     public void setBirthday(Date birthday) {  
  31.         this.birthday = birthday;  
  32.     }  
  33.   
  34.     public List
     getAddresses() {  
  35.         return addresses;  
  36.     }  
  37.   
  38.     public void setAddresses(List
     addresses) {  
  39.         this.addresses = addresses;  
  40.     }  
  41.   
  42.     @Override  
  43.     public String toString() {  
  44.         return "FooBean{" +  
  45.                 "name='" + name + '\'' +  
  46.                 ", id=" + id +  
  47.                 ", birthday=" + birthday +  
  48.                 ", addresses=" + addresses +  
  49.                 '}';  
  50.     }  
  51. }  



 

Java代码    收藏代码
  1. public class Address {  
  2.     private String street;  
  3.     private int number;  
  4.   
  5.     public String getStreet() {  
  6.         return street;  
  7.     }  
  8.   
  9.     public void setStreet(String street) {  
  10.         this.street = street;  
  11.     }  
  12.   
  13.     public int getNumber() {  
  14.         return number;  
  15.     }  
  16.   
  17.     public void setNumber(int number) {  
  18.         this.number = number;  
  19.     }  
  20.   
  21.     @Override  
  22.     public String toString() {  
  23.         return "Address{" +  
  24.                 "street='" + street + '\'' +  
  25.                 ", number=" + number +  
  26.                 '}';  
  27.     }  
  28. }  




当然,结构还可以再复杂,Adress对象里还可以又复杂JavaBean的属性。 

在SpringMVC3中我们可以把输入简单的映射为某个Action方法的参数, 例如: 

Java代码    收藏代码
  1. @RequestMapping(value="/someAction", method=RequestMethod.POST)  
  2. public String processSubmit(FooBean fooBean, Model model) {  
  3. // 利用fooBean  
  4.     return “views/some_page”;  
  5. }  



用Spring MVC3, 我们可以把Form里的字段轻松的映射到JavaBean的属性。 Spring MVC3 提供了丰富的参数映射机制, 详细信息可以参见这里 

同时对于Spring MVC3默认的提供的映射机制不能涵盖的对象,我们可以通过扩展HandlerMethodArgumentResolver和HttpMessageConverter的机制来实现。 
HandlerMethodArgumentResolver对应输入, HttpMessageConverter对应输出 

假设对于上面的FooBean, 我们有这样一个JSON对象和它对应: 

Javascript代码    收藏代码
  1. var data = {  
  2.     name : "matianyi",  
  3.     id : 12345,  
  4.     birthday : "1983-07-01 01:12:12",  
  5.     addresses : [  
  6.         {  
  7.             street : "street1",  
  8.             number : 1  
  9.         },  
  10.         {  
  11.             street : "street2",  
  12.             number : 2  
  13.         }  
  14.     ]  
  15. };  



Spring MVC3 本身也提供直接把JSON对象映射到JavaBean的功能,例如MappingJackson2HttpMessageConverter和MappingJackson2JsonView。 
在这里我们希望通过fastjson来实现序列化和反序列化。所以我们要自定义一个HandlerMethodArgumentResolver用来指定HttpServletRequest的Body映射到一个JavaBean。并且返回的JavaBean通过fastjson序列化。 

方法的定义是这样的: 

Java代码    收藏代码
  1. @RequestMapping(value = "/fastjson", method = RequestMethod.POST)  
  2. public @ResponseBody FooBean fastjson2(@FastJson FooBean foo) {  
  3.     System.out.println(foo);  
  4.     return foo;  
  5. }  



首先这里有个@FastJson的标注,这是我们为了让自己的HandlerMethodArgumentResolver能够识别这个参数是需要自己来处理而定义的一个Annotation 

Java代码    收藏代码
  1. @Target(ElementType.PARAMETER)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Documented  
  4. public @interface FastJson {  
  5. }  



然后就是定义一个FastJsonArgumentResolver,来对HttpServletRequest的body进行解析: 

Java代码    收藏代码
  1. public class FastJsonArgumentResolver implements HandlerMethodArgumentResolver {  
  2.     @Override  
  3.     public boolean supportsParameter(MethodParameter parameter) {  
  4.         return parameter.getParameterAnnotation(FastJson.class) != null;  
  5.     }  
  6.   
  7.     @Override  
  8.     public Object resolveArgument(MethodParameter parameter,  
  9.                                   ModelAndViewContainer mavContainer,  
  10.                                   NativeWebRequest webRequest,  
  11.                                   WebDataBinderFactory binderFactory) throws Exception {  
  12.   
  13.         HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);  
  14.         // content-type不是json的不处理  
  15.         if (!request.getContentType().contains("application/json")) {  
  16.             return null;  
  17.         }  
  18.   
  19.         // 把reqeust的body读取到StringBuilder  
  20.         BufferedReader reader = request.getReader();  
  21.         StringBuilder sb = new StringBuilder();  
  22.   
  23.         char[] buf = new char[1024];  
  24.         int rd;  
  25.         while((rd = reader.read(buf)) != -1){  
  26.             sb.append(buf, 0, rd);  
  27.         }  
  28.   
  29.         // 利用fastjson转换为对应的类型  
  30.         if(JSONObjectWrapper.class.isAssignableFrom(parameter.getParameterType())){  
  31.             return new JSONObjectWrapper(JSON.parseObject(sb.toString()));  
  32.         } else {  
  33.             return JSON.parseObject(sb.toString(), parameter.getParameterType());  
  34.         }  
  35.     }  
  36. }  



在这里,我们只针对content-type是application/json的对象做处理,最后通过JSON.parseObject方法简单的把JSON串反序列化为指定的类型。 

这里有一个JSONObjectWrapper对象需要解释一下。 原本我是想如果Action方法的参数的类型是JSONObject这样的原始类型的话就直接利用JSON.parseObject(sb.toString())映射过去。 但是由于JSONObject实现了Map结果,所以Spring MVC3的默认处理器MapMethodProcessor会先起作用,这样就不能正常的映射成JSONObject对象了。 没有办法做了一个简单的JSONObject包装类,以使MapMethodProcessor不能对其进行处理。 

Java代码    收藏代码
  1. public class JSONObjectWrapper {  
  2.     private JSONObject jsonObject;  
  3.   
  4.     public JSONObjectWrapper(JSONObject jsonObject) {  
  5.         this.jsonObject = jsonObject;  
  6.     }  
  7.   
  8.     public JSONObject getJSONObject() {  
  9.         return jsonObject;  
  10.     }  
  11. }  



这里顺便提一下,Spring MVC自己的HandlerMethodArgumentResolver有哪些,并且会以什么样的顺序执行呢? 
其实定义在RequestMappingHandlerAdapter里: 

Java代码    收藏代码
  1. /** 
  2.  * Return the list of argument resolvers to use including built-in resolvers 
  3.  * and custom resolvers provided via {@link #setCustomArgumentResolvers}. 
  4.  */  
  5. private List getDefaultArgumentResolvers() {  
  6.     List resolvers = new ArrayList();  
  7.   
  8.     // Annotation-based argument resolution  
  9.     resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));  
  10.     resolvers.add(new RequestParamMapMethodArgumentResolver());  
  11.     resolvers.add(new PathVariableMethodArgumentResolver());  
  12.     resolvers.add(new PathVariableMapMethodArgumentResolver());  
  13.     resolvers.add(new MatrixVariableMethodArgumentResolver());  
  14.     resolvers.add(new MatrixVariableMapMethodArgumentResolver());  
  15.     resolvers.add(new ServletModelAttributeMethodProcessor(false));  
  16.     resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));  
  17.     resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));  
  18.     resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));  
  19.     resolvers.add(new RequestHeaderMapMethodArgumentResolver());  
  20.     resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));  
  21.     resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));  
  22.   
  23.     // Type-based argument resolution  
  24.     resolvers.add(new ServletRequestMethodArgumentResolver());  
  25.     resolvers.add(new ServletResponseMethodArgumentResolver());  
  26.     resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));  
  27.     resolvers.add(new RedirectAttributesMethodArgumentResolver());  
  28.     resolvers.add(new ModelMethodProcessor());  
  29.     resolvers.add(new MapMethodProcessor());  
  30.     resolvers.add(new ErrorsMethodArgumentResolver());  
  31.     resolvers.add(new SessionStatusMethodArgumentResolver());  
  32.     resolvers.add(new UriComponentsBuilderMethodArgumentResolver());  
  33.   
  34.     // Custom arguments  
  35.     if (getCustomArgumentResolvers() != null) {  
  36.         resolvers.addAll(getCustomArgumentResolvers());  
  37.     }  
  38.   
  39.     // Catch-all  
  40.     resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));  
  41.     resolvers.add(new ServletModelAttributeMethodProcessor(true));  
  42.   
  43.     return resolvers;  
  44. }  



在这里我们可以看到: 

  • Spring MVC本身提供了非常丰富的HandlerMethodArgumentResolver实现。
  • HandlerMethodArgumentResolver是按顺序执行,当然为了性能Spring本身是有Cache的,一旦确定了某一个参数可以应用的HandlerMethodArgumentResolver,下次就不会再遍历这个List了。
  • 自定的HandlerMethodArgumentResolver会晚于Spring自己的被执行,这也是上面提到的JSONObject会被MapMethodProcessor先处理的原因。
  • Spring自己的JSON映射机制是通过RequestResponseBodyMethodProcessor + AllEncompassingFormHttpMessageConverter来实现的
  • 很不幸这是一个private方法, 你没有办法简单的改变Spring MVC的默认行为,除非你重写RequestMappingHandlerAdapter



好了,有了FastJsonArgumentResolver, 接下来我们要让它生效: 

Xml代码    收藏代码
  1. <mvc:annotation-driven>  
  2.     <mvc:argument-resolvers>  
  3.         <beans:bean class="org.springframework.samples.mvc.fastjson.FastJsonArgumentResolver"/>  
  4.     mvc:argument-resolvers>  
  5.     <mvc:message-converters>  
  6.         <beans:bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>  
  7.     mvc:message-converters>  
  8. mvc:annotation-driven>  



就是个Spring的配置,这里就不多讲了。除了FastJsonArgumentResolver,我们还配置了FastJsonHttpMessageConverter来对返回值进行序列化。 

本来我是想自己写一个FastJsonHttpMessageConverter, 后来发现fastjson库里已经存在了, 我就不自己造轮子了。我们自己来看看实现吧,截取了一部分: 

Java代码    收藏代码
  1. @Override  
  2. protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException,  
  3.                                                                          HttpMessageNotWritableException {  
  4.     OutputStream out = outputMessage.getBody();  
  5.     String text = JSON.toJSONString(obj, features);  
  6.     byte[] bytes = text.getBytes(charset);  
  7.     out.write(bytes);  
  8. }  



实现很简单, 就不详细说了。 

最后来看看如何通过Ajax调用上面的Action方法: 

Java代码    收藏代码
  1. var data = {  
  2.     name : "matianyi",  
  3.     id : 12345,  
  4.     birthday : "1983-07-01 01:12:12",  
  5.     addresses : [  
  6.         {  
  7.             street : "street1",  
  8.             number : 1  
  9.         },  
  10.         {  
  11.             street : "street2",  
  12.             number : 2  
  13.         }  
  14.     ]  
  15. };  
  16. var link = $(this);  
  17. $.ajax({  
  18.     url:"/spring-sample/fastjson1",  
  19.     dataType:"json",  
  20.     type:"POST",  
  21.     contentType: "application/json",  
  22.     data : JSON.stringify(data),  
  23.     success : function(obj){  
  24.         console.log(obj);  
  25.     }  
  26. });  



两点需要注意: 

  • contentType: "application/json"
  • data : JSON.stringify(data)


这样JavaScript的对象会被转换为JSON串,并且最为HttpRequest的BODY传给服务器。 

你可能感兴趣的:(程序日志)