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

自定义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库里已经存在了, 我就不自己造轮子了。我们自己来看看实现吧,截取了一部分:

@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);
}

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

最后来看看如何通过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传给服务器。

最新文章

  1. nohup程序后台执行
  2. 如何在ASP.NET MVC和EF中使用AngularJS
  3. Codeigniter文件上传类型不匹配错误
  4. 30分钟groovy快速入门并掌握(ubuntu 14.04+IntelliJ 13)
  5. buildbot入门系列—介绍篇
  6. Java基础知识学习(六)
  7. 说一下output子句
  8. iOS音频AAC视频H264编码 推流最佳方案
  9. for循环进阶
  10. 将GridView中的数据导出到Excel代码与注意事项
  11. c#事件委托
  12. Google Protocol Buffers和java字符串处理控制
  13. android性能测试内存泄漏
  14. JDBC基础学习(三)&mdash;处理BLOB类型数据
  15. Docker下搭建Jenkins构建环境
  16. 一张图看懂 SQL 的各种 join 用法
  17. Helm 入门指南
  18. deepin配置Oracle JDK
  19. noiac26 T1 (并查集)
  20. Vue(十一)计算属性

热门文章

  1. python网络编程详解
  2. 几道比较难的SQL题
  3. Windows右键菜单设置与应用技巧
  4. 【Linux】撷取命令cut
  5. 转 python 读取中文文件名/中文路径
  6. 以 DirectUI 方式实现的ImageButton
  7. Hibernate的like用法
  8. redis 基本概览
  9. 手机端上传图片及java后台接收和ajaxForm提交
  10. linux shell 命令获取字符串/文件的MD5值