自定义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传给服务器。

SpringMVC HandlerMethodArgumentResolver自定义参数转换器 针对HashMap失效的问题的更多相关文章

  1. SpringMVC HandlerMethodArgumentResolver自定义参数转换器

    来源: https://www.cnblogs.com/daxin/p/3296493.html 自定义Spring MVC3的参数映射和返回值映射 + fastjson首先说一下场景:在一些富客户端 ...

  2. SpringMVC框架——自定义数据类型转换器

    Spring MVC 框架的 Converter<S,T> 是一个可以将一种数据类型转换成另一种数据类型的接口,这里 S 表示源类型,T 表示目标类型. 开发中如果需要自定义数据类型转换时 ...

  3. springmvc:自定义类型转换器代码编写

    字符串转换日期: 1.自定义一个类 /** * 字符串转换日期 */ public class StringToDateConverter implements Converter<String ...

  4. SpringMVC第四篇【参数绑定详讲、默认支持参数类型、自定义参数绑定、RequestParam注解】

    参数绑定 我们在Controller使用方法参数接收值,就是把web端的值给接收到Controller中处理,这个过程就叫做参数绑定- 默认支持的参数类型 从上面的用法我们可以发现,我们可以使用req ...

  5. 《SpringMVC从入门到放肆》十二、SpringMVC自定义类型转换器

    之前的教程,我们都已经学会了如何使用Spring MVC来进行开发,掌握了基本的开发方法,返回不同类型的结果也有了一定的了解,包括返回ModelAndView.返回List.Map等等,这里就包含了传 ...

  6. SpringBoot配置自定义日期参数转换器

    1.自定义参数转换器 自定义参数转换器必须实现Converter接口 /** * Created by IntelliJ IDEA. * * @Auther: ShaoHsiung * @Date: ...

  7. SpringMVC详解------参数绑定

    SpringMVC详解------参数绑定  转载于:https://blog.csdn.net/swebin/article/details/92795422 目录 1.SpringMVC 参数绑定 ...

  8. 阶段3 3.SpringMVC·_02.参数绑定及自定义类型转换_6 自定义类型转换器代码编写

    mvc是基于组件的方式 类型转换的接口Converter,想实现类型转换,必须实现这个接口 Ctrl+N搜索 converter 这是一个接口类 它有很多的实现类.S是字符串.后面T是指要转换类型 新 ...

  9. springmvc 源码分析(三) -- 自定义处理器映射器和自定义处理器适配器,以及自定义参数解析器 和错误跳转自定页面

    测试环境搭建: 本次搭建是基于springboot来实现的,代码在码云的链接:https://gitee.com/yangxioahui/thymeleaf.git DispatcherServlet ...

随机推荐

  1. cxf之Exception in thread "main" java.lang.NoSuchMethodError: org.apache.cxf.jaxrs.provider.ProviderFactory.<init>(Lorg/apache/cxf/Bus;)V

    pom.xml中关于cxf的配置jar包冲突??? 1.http://blog.csdn.net/yzl_8877/article/details/53216923 2.https://www.cnb ...

  2. iOS开发之多媒体播放

    iOS开发之多媒体播放 iOS sdk中提供了很多方便的方法来播放多媒体.本文将利用这些SDK做一个demo,来讲述一下如何使用它们来播放音频文件. AudioToolbox framework 使用 ...

  3. Redis的Docker镜像

    原文地址:https://hub.docker.com/_/redis/ Pull Command docker pull redis Short Description Redis is an op ...

  4. iOS9中怎样在日历App中创建一个随意时间之前開始的提醒(三)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 假设认为写的不好请多提意见,假设认为不错请多多支持点赞.谢谢! hopy ;) 四.创建随意时间之前開始的提醒 如今我们找到了指定源中的指定日 ...

  5. ActiveReports 报表控件官方中文新手教程 (1)-安装、激活以及产品资源

     本系列文章主要是面向初次接触 ActiveReports 产品的用户,能够帮助您在三天之内轻松的掌握ActiveReports控件的基本用法,包含安装.激活.创建报表.绑定数据源以及公布等内容. ...

  6. 使用和学习 ES2015

    调试网站 http://babeljs.io/repl/ 扩展阅读: # export.exports.modules.exports 和 require .import 的一些常用方法和套路 htt ...

  7. Django ORM 数据库操作

    比较有用 转自 http://blog.csdn.net/fgf00/article/details/53678205 一.DjangoORM 创建基本类型及生成数据库表结构 1.简介 2.创建数据库 ...

  8. linux怎么查看一个文件夹的大小

    linux查看一个文件夹的大小的命令为: -lh 该文件夹的完整路径 例,查询/var文件夹的大小: -lh /var du 递归查询该路径下所有文件的大小(若不加任何参数,则显示文件夹内的所有文件, ...

  9. 【转】js frame 框架编程

    源地址:http://www.blogjava.net/lusm/archive/2008/02/11/179620.html 1 框架编程概述 一个Html 页面可以有一个或多个子框架,这些子框架以 ...

  10. Diamond 3.5简易教程(二)------软件的简单使用

    二.软件的简单使用 工程建立后我们就可以进行程序的编写添加了. 选择左下角file list 选项卡 这里主要是工程的信息. 在input files 上右键弹出选项addànew file... 在 ...