本文是对 SpringMVC 中数据绑定的总结。
1、SpringMVC 和 Struts2 的区别
Struts2 和 SpringMVC 都是 Web 开发中视图层的框架,两者都实现了数据的自动绑定,都不需要我们手动获取参数然后关联到对应的属性上,下面就谈谈两者的区别。
- Spring MVC 是基于方法的,通过形参接收参数;Struts2 是基于类的,通过模型驱动封装接收参数。
- SpringMVC 将 url 和 controller 类中的方法映射,生成一个 Handler 对象来执行 method 方法;Struts2 根据配置文件将 url 和 action 类中的方法映射,生成 action 对象来执行 method 方法。
- SpringMVC 形参接收参数,一个方法独享 request response 数据,使用单例开发;Struts2 成员变量接收参数,多个方法共享成员变量,必须使用多例开发。
- SpringMVC 的入口是 Servlet,一个方法对于一个 request 上下文,通过注解将 request 数据注入方法形参;而 Struts2 的入口是 Filter,拦截每个请求,创建一个 Action,调用成员变量的 getter、setter 方法将 reque 数据注入成员变量,两者实现机制不同。
- SpringMVC 更加轻量级,Struts2 配置很多,SpringMVC 开发效率和性能都比 Struts2 高。
- SpringMVC 方法返回的数据更加灵活,使用 AJAX 进行 JSON 交互很方便;Struts2 的标签数据渲染慢,不如 JSTL 标签性能高。
这两个框架我都用过,这里仅是个人看法,Struts2 的配置真的是写死人,类的限制使得使用也不够灵活,与一些前端框架的结合也不是很方便,个人是放弃 Struts2 框架了。
2、不同类型的数据绑定
在开发中前后台交互的数据无非是下面几种:
- 基本类型(int、double、Integer、String 等)
- 对象(类)类型(自定义的实体类)
- 日期类型(java.util.Date)
- 复杂类型(对象数组、List、Set、Map 等)
- 特殊文本类型(JSON、XML 等)
下面就总结一下这些数据在 SpringMVC 中如何绑定到方法形参中。
使用 Maven 来搭建项目,所有的代码都已上传到 GitHub 上,有需要的小伙伴可以前往下载,也欢迎你 star 该仓库哦!
在给方法加上 @ResponseBody 注解后,直接将处理好的数据输出到响应流中,没有了试图解析过程,也就是返回的是 JSON 类型。SpringMVC 这里使用了适配器模式来处理数据转换,当我们使用 Jackson 作为解析 JSON 工具,这里注意一个大坑,Jackson 内默认的编码为 ISO-8859-1(大坑),这就会导致在输出中文时乱码,这点可以通过浏览器的控制台查看,解决方法有以下几种。
1、在每个方法上加上编码设置@RequestMapping(value = "basetype3.do", produces = "application/json; charset=utf-8")
2、在 SpringMVC 配置文件中修改 Jackson 的默认编码为 UTF-8,注意要放在 <mvc:annotation-driven/>
前面,放在内部是不生效的。
3、更改 JSON 解析工具,推荐使用阿里的 fastjson,默认编码就是 UTF-8,解析速度也比 Jackson 快。
方法二、三详细的配置如下:
代码语言:javascript复制<!--特别注意:必须放在mvc:annotation-driven前面,放在内部是不会生效的-->
<!--json装换器使用 Jackson 默认编码是 ISO-8859-1 需要重新设置编码-->
<!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">-->
<!--<property name="messageConverters">-->
<!--<list>-->
<!--<bean class="org.springframework.http.converter.StringHttpMessageConverter">-->
<!--<property name="supportedMediaTypes">-->
<!--<list>-->
<!--<value>text/plain;charset=UTF-8</value>-->
<!--<value>text/html;charset=UTF-8</value>-->
<!--<value>applicaiton/json;charset=UTF-8</value>-->
<!--</list>-->
<!--</property>-->
<!--</bean>-->
<!--</list>-->
<!--</property>-->
<!--</bean>-->
<!-- json装换器使用 fastjson,默认就是 UTF-8 编码,不需要再重新设置编码-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
</list>
</property>
</bean>
<!--配置注解驱动-->
<mvc:annotation-driven/>
说明:项目名为 springmvc,每个方法上面是测试地址哦。
2.1 基本类型
在传参时方法中的形参名称默认要和 url 中的参数名称保持一致,也可以在方法中加 @RequestParam 注解修改 url 中的参数名称。
基本类型中的基本数据类型(int,double)设置为参数是不能为空,否则将会报错,而基本数据类型的包装类型是可以为 null,也即是没有传入时默认值为 null,这里也要注意上面提到的中文乱码哦。
代码语言:javascript复制// http://localhost:8080/springmvc/basetype1.do?id=1
// http://localhost:8080/springmvc/basetype1.do?id= 不带参数报错
@RequestMapping(value = "basetype1.do")
@ResponseBody
public String baseType1(int id) {
return "id=" id;
}
// http://localhost:8080/springmvc/basetype2.do?id=1
// http://localhost:8080/springmvc/basetype2.do?id= 不带参数不报错,参数默认为null
@RequestMapping(value = "basetype2.do")
@ResponseBody
public String baseType2(Integer id) {
return "id=" id;
}
// http://localhost:8080/springmvc/basetype3.do?name='汤姆' 注意中文乱码问题
// http://localhost:8080/springmvc/basetype3.do?name='tom'
@RequestMapping(value = "basetype3.do")
@ResponseBody
public String baseType3(String name) {
return "name=" name;
}
// http://localhost:8080/springmvc/basetype4.do?xid=1
@RequestMapping(value = "basetype4.do")
@ResponseBody
public String baseType4(@RequestParam(value = "xid") Integer id) {
return "id=" id;
}
2.2 对象类型
实体类说明:
- User 类中只有两个属性,一个是 String 类型的 name,一个是 Integer 类型的 age。
- Order 类中也只有两个属性,一个是 String 类型的 id,一个是 User 类型的 user。
- People 类中的属性和 User 类中的完全一样。
类中生成属性的 getter 和 setter 方法以及 toString 方法。
在传对象类型的属性时,url 中参数名称为对象的属性名称,不加对象名。
如果一个类中的属性是另一个类,在传参时,url 中参数名称为属性对象名称加属性,如下面的第二个方法。
当传入的对象类型参数相同时,如果不加以区分,会给同名的属性都赋值,如下面的第三个方法,这里的数据绑定就需要我们自定义,@InitBinder("对象名"),在自定义的方法(方法名任意)中设置属性默认的前缀值,这样就可以区分不同对象的属性了。
代码语言:javascript复制// http://localhost:8080/springmvc/objecttype1.do?name='tom'&age=1
// http://localhost:8080/springmvc/objecttype1.do?name='tom'&age=
@RequestMapping(value = "objecttype1.do")
@ResponseBody
public String objecttype1(User user) {
return "user=" user;
}
// http://localhost:8080/springmvc/objecttype2.do?id='123'&user.name='tom'&user.age=1
// http://localhost:8080/springmvc/objecttype2.do?id='123'&user.name='tom'&user.age=
// http://localhost:8080/springmvc/objecttype2.do?id='123'
@RequestMapping(value = "objecttype2.do")
@ResponseBody
public String objecttype2(Order order) {
return "order=" order;
}
// http://localhost:8080/springmvc/objecttype3.do?people.name=Tom&user.name=Lucy
// http://localhost:8080/springmvc/objecttype3.do?people.name=Tom
// http://localhost:8080/springmvc/objecttype3.do?name=Tom
@RequestMapping(value = "objecttype3.do")
@ResponseBody
public String objecttype3(People people, User user) {
return "people=" people ",user=" user;
}
@InitBinder("people")
public void initPeople(WebDataBinder binder) {
binder.setFieldDefaultPrefix("people.");
}
@InitBinder("user")
public void initUser(WebDataBinder binder) {
binder.setFieldDefaultPrefix("user.");
}
2.3 日期类型
大多数情况下,SpringMVC 的数据绑定以及可以满足我们的使用了,但是对于一些特殊数据类型,如 java.util.Date 类型。字符串转 Date 类型,需要我们自定义转换器(Converter)或格式化(Formatter)来进行数据绑定。
下面的方法一使用绑定数据时会按照用户设置的格式初始化,但这种方法只对单个方法生效,我们可以自定义类型转换类,转换类需要实现 Converter 或者 Formatter 接口,具体的代码如下。
实现 Converter 接口需要指定接口的两个泛型,前者为要转换的类型,后者为转换后的类型,并且需要实现接口中的 convert() 方法,方法中的参数为要转换的类型,返回值为转换后的类型。
实现 Formatter 接口只需要指定接口的一个泛型,即转换后的类型,但是要实现接口中的 parse() 方法和 print() 方法,前一个方法是将要转换的类型转换为我们指定的类型,后一个方法是规定如何输出转换后的类型。
代码语言:javascript复制// DateConverter
public class DateConverter implements Converter<String, Date> {
// 定义日期格式
private String dataPattern = "yyyy-MM-dd HH:mm:ss";
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dataPattern);
try {
return simpleDateFormat.parse(s);
} catch (ParseException e) {
throw new IllegalArgumentException("无效的日期格式,请使用" dataPattern "格式的日期");
}
}
}
代码语言:javascript复制// DateFormatter 类
public class DateFormatter implements Formatter<Date> {
// 定义日期格式
private String dataPattern = "yyyy-MM-dd HH:mm:ss";
@Override
public Date parse(String s, Locale locale) throws ParseException {
return new SimpleDateFormat(dataPattern).parse(s);
}
@Override
public String print(Date date, Locale locale) {
return new SimpleDateFormat().format(date);
}
}
写完自定义转换类后,还需要在 SprinMVC 的配置文件中配置,这样对所有的方法都生效,具体配置如下:
代码语言:javascript复制<!--配置自定义的日期类型转换器-->
<mvc:annotation-driven conversion-service="dataConverterService"/>
<!--使用 Convert 接口-->
<bean id="dataConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.wenshixin.convert.DateConverter"/>
</set>
</property>
</bean>
<!--使用 Formatter 接口-->
<!--<bean id="dataConverterService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">-->
<!--<property name="formatters">-->
<!--<set>-->
<!--<bean class="com.wenshixin.convert.DateFormatter"/>-->
<!--</set>-->
<!--</property>-->
<!--</bean>-->
代码语言:javascript复制// http://localhost:8080/springmvc/datetype1.do?date=2018-09-19
@RequestMapping(value = "datetype1.do")
@ResponseBody
public String datetype1(Date date1) {
return date1.toString();
}
@InitBinder("date1")
public void initDate(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
// http://localhost:8080/springmvc/datetype2.do?date2=2018-09-10 22:50:10
@RequestMapping(value = "datetype2.do")
@ResponseBody
public String datetype2(Date date2) {
return date2.toString();
}
2.4 复杂类型
复杂类型包括数组和集合类型,像 List、Set、Map。
数组类型用于传入多个参数名称相同的值,如接收页面上的复选框参数时。
SpringMVC 对于复杂类型的支持并不是很好,因为对于复杂类型,我们更多都是使用 JSON、XML等数据格式来传参。对于 List、Set、Map 这些类型,还需要单独设置一个包装类,属性设置为对应的集合类型,方法的参数为包装类型,比较繁琐。SpringMVC 对复杂类型的数据绑定的功能,基本上就是鸡肋。
类说明:
- UserList 为 User 对应的 List 集合包装类,只有一个属性,
private List<User> users;
。 - UserList 为 User 对应的 Set 集合包装类,只有一个属性,
private Set<User> users = new HashSet<>();
,并且需要在该类的构造函数中初始化 Set 集合的大小,不能动态改变 Set 集合大小,在传值时,对象的个数不能超过这个大小。 - UserList 为 User 对应的 Map 集合包装类,只有一个属性,
private Map<String, User> users;
。
// http://localhost:8080/springmvc/complextype1.do?ids=1&ids=2
@RequestMapping(value = "complextype1.do")
@ResponseBody
public String objecttype1(String[] ids) {
System.out.println(ids.length);
StringBuilder stringBuilder = new StringBuilder();
for(String id : ids) {
stringBuilder.append(id " ");
}
return stringBuilder.toString();
}
// http://localhost:8080/springmvc/complextype2.do?users[0].name=Tom&users[1].name=Lucy 注意特殊字符[]的转义,不然会报错
// http://localhost:8080/springmvc/complextype2.do?users[0].name=Tom&users[1].name=Lucy&users[6].name=Mary 注意特殊字符[]的转义,不然会报错
@RequestMapping(value = "complextype2.do")
@ResponseBody
public String objecttype2(UserList userList) {
return userList.toString();
}
// http://localhost:8080/springmvc/complextype2.do?users[0].name=Tom&users[1].name=Lucy&users[2].name=Mary 注意特殊字符[]的转义,不然会报错
@RequestMapping(value = "complextype3.do")
@ResponseBody
public String objecttype3(UserSet userSet) {
System.out.println(userSet.getUsers().size());
return userSet.toString();
}
// http://localhost:8080/springmvc/complextype4.do?users['0'].name=Tom&users['1'].name=Lucy&users['2'].name=Mary
@RequestMapping(value = "complextype4.do")
@ResponseBody
public String objecttype4(UserMap userMap) {
System.out.println(userMap.getUsers().size());
return userMap.toString();
}
2.5 特殊类型
SpringMVC 更适合现今前后端分离的数据传输,对于现在流行的格式化数据类型 JSON,支持很好,只需要 @RequestBody(传参)和 @ResponseBody(输出)两个注解,使用起来很方便。
对于编写 API,SpringMVC 无疑是比 Struts2 的有优势。
代码语言:javascript复制// json 格式
/*
{
"name":"Tom",
"age":1
}
*/
@RequestMapping(value = "jsontype.do")
@ResponseBody
public User jsontype(@RequestBody User user) {
System.out.println(user);
return user;
}
// xml 格式
/*
<?xml version="1.0" encoding="UTF-8" ?>
<user>
<name>Jim</name>
<age>16</age>
</user>
*/
@RequestMapping(value = "xmltype.do")
@ResponseBody
public User xmltype(@RequestBody User user) {
System.out.println(user);
return user;
}
2.6 RESTful 风格
RESTful 风格的 API 已经受到业界的肯定,在当今的分布式架构中更是如鱼得水。很多 Web 框架也都支持 RESTful 风格的 API编写,当然也包括 SpringMVC ,这里简单介绍一下 RESTful 风格。
RESTful 是 Resource Representional State Transfer 的缩写,RE 是前面两个单词的简写,第一个单词经常被省略,而这个单词其实才是 RESTful 的核心思想,中文翻译为 资源表现层状态转换。
RESTful 的作者也是 HTTP 协议的设计者,他将 HTTP 中的 URI 的思想引入到 API 编程中,每一个资源都有一个存放的位置,对资源的操作(请求)就是资源在表现层的转态转换,如常见的 GET、POST,还有不常用 PUT、DELETE 等。
RESTful 风格有更加简短的资源地址,和一般的 API 地址直接对资源进行操作,如 add、select 不同,RESTful 风格的主体是资源,对资源的操作体现在请求方式上,如 DELETE。不同的请求方式对应不同的操作,如同一个地址,如果是 GET 方式,就直接返回页面,如果是 POST 方式,就是提交页面上的数据,这样地址也更少,使得访问也更加安全。
下面的代码展示了 RESTful 风格的 API 如何使用,API 的测试,用浏览器并不方便,可以使用 Postman 等网络工具。
代码语言:javascript复制@RequestMapping(value = "/user/{name}", method = RequestMethod.GET)
@ResponseBody
public String findUserByGET(@PathVariable("name") String name) {
return "GET name=" name;
}
@RequestMapping(value = "/user/{name}", method = RequestMethod.POST)
@ResponseBody
public String findUserByPOST(@PathVariable("name") String name) {
return "POST name=" name;
}