Spring MVC 数据绑定流程
前端表单穿过了的数据,为什么会被自动封装成实体对象?Spring MVC 是如何把我们的实体类和表单中参数对应封装起来,又是如何完成自动类型转换呢?
数据绑定流程:
- Spring MVC 框架将 ServletRequest 对象及目标方法的入参实例传给 WebDataBinderFactory 实例,这个实例便用来创建 DataBinder 对象
- DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据的类型转换和格式化,将 ServletRequest 请求参数绑定到对应目标方法的入参对象中。
- 随后 DataBinder 调用 Validator 组件对已经绑定到目标方法入参对象进行数据校验,并把最终形成的结果到 BindingData 对象
- 然后把其中格式、类型转换和数据校验发生的错误失败信息,都保存到 BindingResult
理论很复杂,我们可以实际应用看看。
格式化和类型转换器
类型转换器
Spring 中有着许多的类型转换器,可以完成大多数 Java 类型转换,但是如果遇到某些常见,比如把一个字符串封装成一个 实体类,那么就需要自定义类型转换器了。
ConversionService 是类型转换器的接口。我们可以利用 ConversionServiceFactoryBean 这个实现类来生产 ConversionService。
在 SpringIOC 容器定义 ConversionServiceFactoryBean ,Spring 将自动识别到这个实体,并且在 bean 属性设置及 SpringMVC 处理方法入参绑定等场合使用这个 ConversionServiceFactoryBean。查看该类的源码
public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
@Nullable
private Set<?> converters;
@Nullable
private GenericConversionService conversionService;
public void setConverters(Set<?> converters) {
this.converters = converters;
}
...
...
...
}
可以看到一个 Set 集合,用来保存自定义注册的类型转换器。我们可以通过在 XML 中属性注入来导入自己的类型转换器。
Spring 定义了三种类型转换器,实现其中一个便可以自定义类型转换。
- Converter<S,T>
- 表示把 S 类型转为 T 类型
- ConverterFactory
- GenericConverter
如何使用?
范例:把 MrWang-xiaowang@163.com-1-18
转为一个 Student 实例。
-
编写界面
<form action="${pageContext.request.contextPath}/insertStu" method="post" > <input type="text" name="emp" value="MrWang-xiaowang@163.com-1-18"><br> <input type="submit" value="提交"> </form>
-
编写目标处理器
@RequestMapping(value = "insertStu", method = RequestMethod.POST, produces = "text/html;charset=UTF-8") public String insertEmp1(Student stu) { System.out.println(stu); stuManagerService.insertStu(stu); return "success.jsp"; }
-
编写自定义类型转换器,实现 Converter 接口
public class StudentConverter implements Converter<String, Student>{ @Override public Student convert(String arg0) { String[] stu = arg0.split("-"); Student s = new Student(); s.setStuName(stu[0]); s.setEmpMail(stu[1]); s.setStuGender(Integer.valueOf(stu[2])); s.setStuAge(Integer.valueOf(stu[3])); return s; } }
-
配置 ConversionServiceFactoryBean 和自定义类型转换类型的 bean
<bean id="studentConverter" class="com.nhky.converter.StudentConverter"></bean> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="studentConverter"></ref> </set> </property> </bean>
格式化
说完了类型转换器,那么格式化?
对于格式化,它也属于类型转换的范畴。SpringMVC 的 ConversionService 有一个实现类 FormattingConversionService ,它有着类型转换和格式化的功能,并且该类也有一个工厂类FormattingConversionServiceFactoryBean 。它可以用来实现 FormattingConversionService。
并且在内部,提供了一些注解来实现格式化,例如
- @NumberFormat
- 用于数字类型的格式化
- @DateFormat
- 用于日期类型的格式化
修改上面的例子,在 Student 加入 money 和 birthday 属性。制定格式化内容。
-
修改表单
<form id="stu" action="${pageContext.request.contextPath}/stu" method="post"> 姓名:<input id="stuName" name="stuName" type="text" value=""> <br> 邮箱:<input id="stuMail" name="stuMail" type="text" value=""> <br> 性别:<input id="stuGender" name="stuGender" type="text" value=""> <br> 年龄:<input id="stuAge" name="stuAge" type="text" value=""> <br> 零花钱:<input id="money" name="money" type="text" value=""><br> 生日:<input id="birthday" name="birthday" type="text" value=""><br> <button type="submit" value="Submit">提交</button> </form>
-
修改实体类字段
@NumberFormat(pattern="#,###,###,###.#") private Double money; @DateTimeFormat(pattern="yyyy-MM-dd") private Date birthday;
-
配置 FormattingConversionServiceFactoryBean
<bean id="studentConverter" class="com.nhky.converter.StudentConverter"></bean> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="studentConverter"></ref> </set> </property> </bean>
失败信息
上面如果失败,例如发生类型转换错误,格式化失败,都会把失败信息保存到 BindingResult。可以在上面的目标方法入参加上 BindingResult 来获取错误信息。(获取了这个错误信息,页面也不会跳转 404 等错误界面)
@RequestMapping(value = "insertStu", method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
public String insertEmp1(Student stu,BindingResult result) {
System.out.println(stu);
List<FieldError> errors = result.getFieldErrors();
for (FieldError fieldError : errors) {
System.out.println(fieldError.getField() + "-----" + fieldError.getDefaultMessage());
}
return "success.jsp";
}
数据校验
JSR 303 是 Java 为 Bean 数据的合法性提供的一个标准框架,在 JavaEE 6.0 中,有着例如 @NULL,@NOTNULL,@MAX 等标准的注解指定校验规则。几个常用的如下:
序号 | 注解 | 说明 |
---|---|---|
1 | @Null | 被注解的属性必须为NULL |
2 | @NotNull | 被注解的属性必须不能为空 |
3 | @Past | 必须是一个过去的日期 |
4 | @Future | 必须是一个将来的日期 |
5 | @Pattern | 必须满足指定的正则规则 |
6 | @Max | 不能大于指定的最大值 |
7 | @Min | 不能小于指定的最小值 |
8 | @Future | 限制必须是一个将来的日期 |
范例:使用 @Email ,@Range 等注解进行数据校验
首先要导入 Hibernate Validator 扩展注解。
-
jar 包
classmate-0.8.0.jar hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar jboss-logging-3.1.1.GA.jar validation-api-1.1.0.CR1.jar
-
Hibernate Validator 扩展了以下几个注解
-
@Email:必须符合电子邮箱格式
-
@Length:字符串长度必须在指定范围内
-
@NotEmpty:必须非空(包括空串)
-
@Range:必须在指定的返回内
-
-
在实体类上添加注解
public class Student { private Integer stuId; @NotEmpty private String stuName; @NotEmpty @Email private String stuMail; private Integer stuGender; private Integer stuAge; @Range(max = 8000,min = 2000) @NumberFormat(pattern = "#,###,###,###.#") private Double money; @Past @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birthday; .... // getter setter 方法 }
-
表单(这边要导入
<%@taglib prefix="f" uri="http://www.springframework.org/tags/form" %>
)<f:form modelAttribute="stdu"> 姓名:<f:input path="stuName"/> <br> 邮箱:<f:input path="stuMail"/> <br> 性别:<f:input path="stuGender" /> <br> 年龄:<f:input path="stuAge"/> <br> 零花钱:<f:input path="money"/><br> 生日:<f:input path="birthday"/><br> <f:button >提交</f:button> </f:form>
-
编写目标处理器
@RequestMapping(value="stu",method = RequestMethod.GET) public String getForm(Map map) { Student stu = new Student(); map.put("stu", stu); return "input2.jsp"; } @RequestMapping(value="stu",method = RequestMethod.POST) public String getForm1(@Valid Student stu,BindingResult result,Map map) { map.put("stu", stu); List<FieldError> errors = result.getFieldErrors(); for (FieldError fieldError : errors) { System.out.println(fieldError.getField() + "-----" + fieldError.getDefaultMessage()); } return "input2.jsp"; }
-
直接访问
http://localhost:8080/XXXX/stu
,输入如下信息我们就可以看到控制台打印如下:
money-----需要在2000和8000之间 stuName-----不能为空 birthday-----需要是一个过去的事件 stuMail-----不能为空
-
注意:
- 如果 @Valid 报 400 错误。可能是 @Valid 后面如果使用 BindingResult ,那么 @Valid 所带的参数后面必须接上参数 BindingResult 这个参数,如果中间隔着其它会出现400错误。