1. 概述
在执行程序时,Spring MVC会根据客户端请求参数的不同,将请求消息中的信息以一定的方式转换并绑定到控制器类的方法参数中。这种将请求消息数据与后台方法参数建立连接的过程就是Spring MVC中的数据绑定
在早期springMVC使用PropertyEditor
,后期使用convertor
进行任意类型的转换,springMVC提供了很多convertor
(转换器),在特使情况下(日期数据的绑定)需要自定义convertor
数据绑定的流程
SpringMVC 有支持的默认参数类型,我们直接在形参上给出这些默认类型的声明,就能直接使用了
//HttpServletRequest 对象
//HttpServletResponse 对象
//HttpSession 对象
//Model/ModelMap 对象
@RequestMapping("/defaultParameter")
public ModelAndView defaultParameter(HttpServletRequest request,HttpServletResponse response,HttpSession session,Model model,ModelMap modelMap) throws Exception{
request.setAttribute("requestParameter", "request类型");
response.getWriter().write("response");
session.setAttribute("sessionParameter", "session类型");
//ModelMap是Model接口的一个实现类,作用是将Model数据填充到request域
//即使使用Model接口,其内部绑定还是由ModelMap来实现
model.addAttribute("modelParameter", "model类型");
modelMap.addAttribute("modelMapParameter", "modelMap类型");
ModelAndView mv = new ModelAndView();
mv.setViewName("view/success.jsp");
return mv;
}
而对于其他类型的参数,SpringMVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中,数据绑定的核心部件是DataBinder
① SpringMVC主框架将ServletRequest
对象以及目标法方法的入参传递给WebDataBinderFactory
实例,以创建DataBinder
(数据绑定器)实例对象
② DataBinder
调用装配在SpringMVC上下文的ConversionService
组件进行数据类型转换,数据格式化工作,将Servlet
中的请求信息填充到入参对象中
③ 调用Validator
组件对已经绑定了请求参数的入参对象进行数据合法性校验,并最终生成绑定结果BindingData
对象,如果在校验过程中出现错误,结果会放到BindingResult
对象中
④ 校验完成后会生成数据绑定结果BindingResult
对象,Spring MVC会将BindingResult对象中的内容赋给处理方法的相应参数
2. 数据绑定
① 基本类型参数
包括基本类型和 String 类型
使用要求:参数名称必须和控制器中方法的形参名称保持一致。 (严格区分大小写)
index.jsp
<form action="basicData" method="post">
<input name="username" value="10" type="text"/>
<input type="submit" value="提交">
</form>
Controller 代码:
@RequestMapping("/basicData")
public void basicData(int username){
System.out.println(username);//10
}
我们这里的参数是基本数据类型,如果从前台页面传递的值为 null 或者 “”的话,那么会出现数据转换的异常,就是必须保证表单传递过来的数据不能为null或”",所以,在开发过程中,对可能为空的数据,最好将参数数据类型定义成包装类型
index.jsp
<form action="${pageContext.request.contextPath}/user/testParam" method="post">
用户名: <input type="text" name="username"> <br>
年龄: <input type="text" name="age"> <br>
性别: <input type="text" name="sex"> <br>
<input type="submit" value="提交">
</form>
UserController.java
@Controller
@RequestMapping("/user")
public class UserController2 {
@RequestMapping(value = "/testParam",method = RequestMethod.POST)
public String testParam(String username,Integer age,Character sex){
System.out.println(username + "--" + age + "--" + sex);
return "show";
}
}
表单传递过来的数据可以为null或”",以上面代码为例,如果表单中num为”"或者表单中无num这个input,那么,Controller方法参数中的num值则为null
② POJO类型参数
- 要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同。
index.jsp
<form action="${pageContext.request.contextPath}/user/saveUser" method="post">
用户名: <input type="text" name="username"> <br>
年龄: <input type="text" name="age"> <br>
性别: <input type="text" name="sex"> <br>
一个角色对象:<!--role为 POJO类型参数-->
id:<input type="text" name="role.id">
roleName:<input type="text" name="role.roleName"><br>
<input type="submit" value="提交">
</form>
Role.java
public class Role {
private Integer id;
private String roleName;
//此处省略 getter and setter toString
}
User.java
public class User {
private Integer id;
private String username;
private Character sex;
private Integer age;
private Role role; //POJO 类型参数
//此处省略 getter and setter toString
}
UserController.java
@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public String saveUser(User user){
System.out.println(user);
return "show";
}
③ 集杂数据类型
数组
表单中name属性相同,value不同;形参是数组,且名称相同
集合
-
要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同
-
给 List 集合中的元素赋值,使用下标
-
给 Map 集合中的元素赋值,使用键值对
index.jsp
<form action="${pageContext.request.contextPath}/user/saveUser" method="post">
用户名: <input type="text" name="username"> <br>
年龄: <input type="text" name="ag"> <br>
性别: <input type="text" name="sex"> <br>
list集合参数: <input type="text" name="list[0]">
<input type="text" name="list[1]"><br>
一个角色对象:<input type="text" name="role.id">
<input type="text" name="role.roleName"><br>
多个角色对象:<input type="text" name="roleList[0].id">
<input type="text" name="roleList[0].roleName"><br>
<input type="text" name="roleList[1].id">
<input type="text" name="roleList[1].roleName"><br>
map集合:<input type="text" name="map[one]"><br>
<input type="text" name="map[two]"><br>
<input type="text" name="map[three]"><br>
<input type="submit" value="提交">
</form>
Role.java
public class Role {
private Integer id;
private String roleName;
//此处省略 getter and setter toString
}
User.java
public class User {
private Integer id;
private String username;
private Character sex;
private Integer age;
private List<String> list;
private Role role;
private List<Role> roleList;
private Map<String ,Object> map;
//此处省略 getter and setter toString
}
UserController.java
@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public String saveUser(User user){
System.out.println(user);
return "show";
}
后台结果
User {
id = null,
username = 'zhangsan',
sex = 男,
age = 30,
list = [hello, world],
role = Role {
id = 21,
roleName = 'cook'
},
roleList = [Role {
id = 23,
roleName = 'teacher'
}, Role {
id = 25,
roleName = 'student'
}
],
map = {
one = little,
three = huge,
two = big
}
}
3. 自定义类型转换器
-
表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明Spring框架内部会默认进行数据类型转换。
-
如果想自定义数据类型转换,可以实现Converter的接口
-
注册自定义类型转换器,在springmvc.xml配置文件中编写配置
-
还是可以使用Formatter进行类型转换
当我们在后端对日期类型的参数进行准换,默认只转换2000/1/1类型的日期,如果输入的是2000-1-1转换就会出错
ConversionService
是Spring类型转换体系的核心接口
可以利用ConversionServiceFactoryBean
在Spring的IOC容器中定义一个ConversionService
,Spring将自动识别出IOC容器中的ConversionService
,并在Bean属性配置及Spring MVC处理方法入参绑定等场合使用它进行数据的转换
可以通过ConversionServiceFactoryBean
的converters
属性注册自定义的类型转换器
<form action="${pageContext.request.contextPath}/user/testDate">
<input type="date" name="birthday">
<input type="submit" value="提交">
</form>
@RequestMapping("/testDate")
public String testDate(Data birthday){
System.out.println(birthday);
return "show";
}
自定义String–>Date类型转换器
public class StringToDateConverter implements Converter<String ,Date>{
//把 String 类型转成日期类型
@Override
public Date convert(String source) {
//日期类型的转换对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
在 spring 配置文件中配置类型转换器
<!-- 配置类型转换器工厂 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器 -->
<property name="converters">
<set>
<!-- 配置自定义类型转换器 -->
<bean class="com.testfan.converter.StringToDateConverter"></bean>
</set>
</property>
</bean>
在 annotation-driven 标签中引用配置的类型转换服务
<!-- 引用自定义类型转换器 -->
<!--注解驱动: 关联类型转换工厂-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
4. 数据格式化
数据格式化
Converter
可以将一种类型转换成另一种类型,是任意Object
之间的类型转换
Formatter
则只能进行String
与任意Object
对象的转换,它提供 解析 与 格式化 两种功能
其中:解析是将String
类型字符串转换为任意Object
对象,格式化是将任意Object
对象转换为字符串进行格式化显示
① 实现Formatter<T>
接口
DateFormatter.java
//实现Formatter<T> 接口
public class DateFormatter implements Formatter<Date>{
// 日期类型模板:如yyyy-MM-dd
private String datePattern;
// 日期格式化对象
private SimpleDateFormat dateFormat;
// 构造器,通过依赖注入的日期类型创建日期格式化对象
public DateFormatter(String datePattern) {
this.datePattern = datePattern;
this.dateFormat = new SimpleDateFormat(datePattern);
}
// 显示Formatter<T>的T类型对象
@Override
public String print(Date date, Locale locale) {
return dateFormat.format(date);
}
// 解析文本字符串返回一个Formatter<T>的T类型对象。
@Override
public Date parse(String source, Locale locale) throws ParseException {
try {
return dateFormat.parse(source);
} catch (Exception e) {
throw new IllegalArgumentException();
}
}
}
装配自定义格式化转换器
<!-- 装配自定义格式化转换器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<list>
<bean class="com.formatter.DataFormatter" c:_0="yyyy-MM-dd"></bean>
</list>
</property>
</bean>
② 使用注解
// 域对象,实现序列化接口
public class User implements Serializable{
// 日期类型
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthday;
// 正常数字类型
@NumberFormat(style=Style.NUMBER, pattern="#,###")
private int total;
// 百分数类型
@NumberFormat(style=Style.PERCENT)
private double discount;
// 货币类型
@NumberFormat(style=Style.CURRENCY)
private double money;
...
}
5. 数据校验
数据校验
JSR 303 是Java为Bean数据合法性校验提供的标准框架,其已经包含在JavaEE 6.0中;JSR 303 通过在Bean属性上标注类似于@NotNull、@Max
等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,还支持一些扩展注解
① 关于所需要的jar包
Spring4.0拥有独立的数据校验框架,同时支持 JSR 303 标准的校验框架;但其本身并没有提供 JSR 303 的实现,故必须将 JSR 303 的实现者的jar包放到类路径下。
② 关于LocalValidatorFactoryBean
工厂类
该工厂类既实现了Spring的Validator
接口,也实现了 JSR 303 的Validator接口;故需要在 Spring 容器中定义LocalValidatorFactoryBean
,即可将其注入到需要数据校验的Bean
中。
③ 关于@Valid
注解
<mvc:annotation-driven/>
会默认装配LocalValidatorFactoryBean
,通过在处理方法的入参上标注@Valid
注解即可让SpringMVC在进行数据绑定时,同时调用校验框架完成数据校验工作。
④ 关于校验结果
前一个表单/命令对象的校验结果保存到随后处理方法的入参中,该入参必须是BindingResult
或Errors
类型;
需注意,需校验的Bean对象和其绑定结果对象或错误对象是成对出现的,其之间不允许声明其他的入参
在需要校验的JavaBean
属性上添加相应的校验注解
public class Employee {
@NotNull
private String name;
@Email
private String email;
@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
// ……
}
在处理器目标方法的Bean类型的入参前添加@Valid注解,并添加保存校验结果的对象(BindingResult
)
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
System.out.println(employee);
//BindingResult
if(result.getErrorCount() > 0) {
for(FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + " : " + error.getDefaultMessage());
}
// 指定校验错误时所转向的定制页面
map.put("departments", departmentDao.getDepartments());
return "emp-edit";
}
employeeDao.save(employee);
return "redirect:/emps";
}