来源: https://www.cnblogs.com/daxin/p/3296493.html

自定义Spring MVC3的参数映射和返回值映射 + fastjson首先说一下场景:在一些富客户端Web应用程序中我们会有比较多的Ajax调用,并且希望与服务器交互的数据需要是复杂的JSON对象。 fastjon是一个非常高效的JSON序列化和反序列化库,我希望我们输入的JSON串能通过fastjson直接反序列化为一个复杂的JavaBean对象,同时我的返回值能够能通过fastjson序列化为JSON串。所谓复杂的JavaBean就是,不仅仅只有一层属性,而是属性也是JavaBean的情况, 例如:

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
public class FooBean { private String name; private Long id; private Date birthday; private List<Address> addresses; public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public List<Address> getAddresses() { return addresses; } public void setAddresses(List<Address> addresses) { this.addresses = addresses; } @Override public String toString() { return "FooBean{" + "name='" + name + ''' + ", id=" + id + ", birthday=" + birthday + ", addresses=" + addresses + '}'; } }
public class Address { private String street; private int number; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public String toString() { return "Address{" + "street='" + street + ''' + ", number=" + number + '}'; } }

 

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

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

 

@RequestMapping(value="/someAction", method=RequestMethod.POST)
public String processSubmit(FooBean fooBean, Model model) { // 利用fooBean return “views/some_page”; }

 

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

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

HandlerMethodArgumentResolver对应输入, HttpMessageConverter对应输出

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

 

var data = { name : "matianyi", id : 12345, birthday : "1983-07-01 01:12:12", addresses : [ { street : "street1", number : 1 }, { street : "street2", number : 2 } ] };

 

Spring MVC3 本身也提供直接把JSON对象映射到JavaBean的功能,例如MappingJackson2HttpMessageConverter和MappingJackson2JsonView。

在这里我们希望通过fastjson来实现序列化和反序列化。所以我们要自定义一个HandlerMethodArgumentResolver用来指定HttpServletRequest的Body映射到一个JavaBean。并且返回的JavaBean通过fastjson序列化。

方法的定义是这样的:

@RequestMapping(value = "/fastjson", method = RequestMethod.POST)
public @ResponseBody FooBean fastjson2(@FastJson FooBean foo) { System.out.println(foo); return foo; }

 

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

 

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FastJson { }

 

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

 

public class FastJsonArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterAnnotation(FastJson.class) != null; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); // content-type不是json的不处理 if (!request.getContentType().contains("application/json")) { return null; } // 把reqeust的body读取到StringBuilder BufferedReader reader = request.getReader(); StringBuilder sb = new StringBuilder(); char[] buf = new char[1024]; int rd; while((rd = reader.read(buf)) != -1){ sb.append(buf, 0, rd); } // 利用fastjson转换为对应的类型 if(JSONObjectWrapper.class.isAssignableFrom(parameter.getParameterType())){ return new JSONObjectWrapper(JSON.parseObject(sb.toString())); } else { return JSON.parseObject(sb.toString(), parameter.getParameterType()); } } }

 

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

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

public class JSONObjectWrapper { private JSONObject jsonObject; public JSONObjectWrapper(JSONObject jsonObject) { this.jsonObject = jsonObject; } public JSONObject getJSONObject() { return jsonObject; } }

 

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

/** * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */ private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }

 

在这里我们可以看到:

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

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

<mvc:annotation-driven> <mvc:argument-resolvers> <beans:bean class="org.springframework.samples.mvc.fastjson.FastJsonArgumentResolver"/> </mvc:argument-resolvers> <mvc:message-converters> <beans:bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven>

 

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

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

 

SpringMVC HandlerMethodArgumentResolver自定义参数转换器 随笔 第1张
@Override
protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream out = outputMessage.getBody(); String text = JSON.toJSONString(obj, features); byte[] bytes = text.getBytes(charset); out.write(bytes); }
SpringMVC HandlerMethodArgumentResolver自定义参数转换器 随笔 第2张

 

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

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

var data = {
    name : "matianyi",
    id : 12345,
    birthday : "1983-07-01 01:12:12",
    addresses : [
        {
            street : "street1",
            number : 1
        },
        {
            street : "street2",
            number : 2
        }
    ]
};
var link = $(this);
$.ajax({
    url:"/spring-sample/fastjson1",
    dataType:"json",
    type:"POST",
    contentType: "application/json",
    data : JSON.stringify(data),
    success : function(obj){
        console.log(obj);
    }
});

两点需要注意:

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

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

,

来源: https://www.cnblogs.com/daxin/p/3296493.html

自定义Spring MVC3的参数映射和返回值映射 + fastjson首先说一下场景:在一些富客户端Web应用程序中我们会有比较多的Ajax调用,并且希望与服务器交互的数据需要是复杂的JSON对象。 fastjon是一个非常高效的JSON序列化和反序列化库,我希望我们输入的JSON串能通过fastjson直接反序列化为一个复杂的JavaBean对象,同时我的返回值能够能通过fastjson序列化为JSON串。所谓复杂的JavaBean就是,不仅仅只有一层属性,而是属性也是JavaBean的情况, 例如:

public class FooBean { private String name; private Long id; private Date birthday; private List<Address> addresses; public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public List<Address> getAddresses() { return addresses; } public void setAddresses(List<Address> addresses) { this.addresses = addresses; } @Override public String toString() { return "FooBean{" + "name='" + name + ''' + ", id=" + id + ", birthday=" + birthday + ", addresses=" + addresses + '}'; } }
public class Address { private String street; private int number; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public String toString() { return "Address{" + "street='" + street + ''' + ", number=" + number + '}'; } }

 

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

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

 

@RequestMapping(value="/someAction", method=RequestMethod.POST)
public String processSubmit(FooBean fooBean, Model model) { // 利用fooBean return “views/some_page”; }

 

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

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

HandlerMethodArgumentResolver对应输入, HttpMessageConverter对应输出

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

 

var data = { name : "matianyi", id : 12345, birthday : "1983-07-01 01:12:12", addresses : [ { street : "street1", number : 1 }, { street : "street2", number : 2 } ] };

 

Spring MVC3 本身也提供直接把JSON对象映射到JavaBean的功能,例如MappingJackson2HttpMessageConverter和MappingJackson2JsonView。

在这里我们希望通过fastjson来实现序列化和反序列化。所以我们要自定义一个HandlerMethodArgumentResolver用来指定HttpServletRequest的Body映射到一个JavaBean。并且返回的JavaBean通过fastjson序列化。

方法的定义是这样的:

@RequestMapping(value = "/fastjson", method = RequestMethod.POST)
public @ResponseBody FooBean fastjson2(@FastJson FooBean foo) { System.out.println(foo); return foo; }

 

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

 

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FastJson { }

 

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

 

public class FastJsonArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterAnnotation(FastJson.class) != null; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); // content-type不是json的不处理 if (!request.getContentType().contains("application/json")) { return null; } // 把reqeust的body读取到StringBuilder BufferedReader reader = request.getReader(); StringBuilder sb = new StringBuilder(); char[] buf = new char[1024]; int rd; while((rd = reader.read(buf)) != -1){ sb.append(buf, 0, rd); } // 利用fastjson转换为对应的类型 if(JSONObjectWrapper.class.isAssignableFrom(parameter.getParameterType())){ return new JSONObjectWrapper(JSON.parseObject(sb.toString())); } else { return JSON.parseObject(sb.toString(), parameter.getParameterType()); } } }

 

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

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

public class JSONObjectWrapper { private JSONObject jsonObject; public JSONObjectWrapper(JSONObject jsonObject) { this.jsonObject = jsonObject; } public JSONObject getJSONObject() { return jsonObject; } }

 

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

/** * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */ private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }

 

在这里我们可以看到:

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

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

<mvc:annotation-driven> <mvc:argument-resolvers> <beans:bean class="org.springframework.samples.mvc.fastjson.FastJsonArgumentResolver"/> </mvc:argument-resolvers> <mvc:message-converters> <beans:bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven>

 

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

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

 

SpringMVC HandlerMethodArgumentResolver自定义参数转换器 随笔 第3张
@Override
protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream out = outputMessage.getBody(); String text = JSON.toJSONString(obj, features); byte[] bytes = text.getBytes(charset); out.write(bytes); }
SpringMVC HandlerMethodArgumentResolver自定义参数转换器 随笔 第4张

 

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

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

var data = {
    name : "matianyi",
    id : 12345,
    birthday : "1983-07-01 01:12:12",
    addresses : [
        {
            street : "street1",
            number : 1
        },
        {
            street : "street2",
            number : 2
        }
    ]
};
var link = $(this);
$.ajax({
    url:"/spring-sample/fastjson1",
    dataType:"json",
    type:"POST",
    contentType: "application/json",
    data : JSON.stringify(data),
    success : function(obj){
        console.log(obj);
    }
});

两点需要注意:

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

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

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄