验证以及国际化
2023年11月15日大约 12 分钟约 2336 字
介绍
- 对输入的数据(比如表单数据),进行必要的验证,并给出相应的提示信息。
- 对于验证表单数据,SpringMVC 提供了很多实用的注解,这些注解由 JSR 303 验证框架提供。
JSR 303 验证框架
- JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 中。
- JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则, 并通过标准的验证接口对 Bean 进行验证。
- JSR 303 提供的基本验证注解有:

Hibernate Validator 扩展注解
- Hibernate Validator 和 Hibernate 没有关系,只是 JSR 303 实现的一个扩展。
- Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:

应用实例
引入验证和国际化相关的 jar 包

修改 Monster.java
//@Range(min = 1,max = 100)
//表示接收的age值,在 1-100之间
@Range(min = 1, max = 100)
private Integer age;
//@NotEmpty 表示name不能为空
//Asserts that the annotated string, collection, map or array is not {@code null} or empty.
@NotEmpty
private String name;
修改 MonsterHandler.java
/**
* 编写方法,处理添加妖怪
* 1. springmvc可以将提交的数据,按照参数名和对象的属性名匹配
* 2. 直接封装到对象中
* String => Integer
* 3. @Valid Monster monster :表示对monster接收的数据进行校验
* 4. Errors errors 表示如果校验出现错误,将校验的错误信息保存 errors
* 5. Map<String, Object> map 表示如果校验出现错误, 将校验的错误信息保存 map 同时保存monster对象
* 6. 校验发生的时机: 在springmvc底层,反射调用目标方法时,会接收到http请求的数据,然后根据注解来进行验证
* , 在验证过程中,如果出现了错误,就把错误信息填充errors 和 map
* @param monster
* @return
*/
@RequestMapping(value = "/save")
public String save(@Valid Monster monster, Errors errors, Map<String, Object> map){
System.out.println("----monster---" + monster);
//我们为了看到验证的情况,我们输出map 和 errors
System.out.println("===== map ======");
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " value=" + entry.getValue());
}
System.out.println("===== errors ======");
if (errors.hasErrors()) {//判断是否有错误
List<ObjectError> allErrors = errors.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println("error=" + error);
}
return "datavalid/monster_addUI";
}
return "datavalid/success";
}
修改 web/WEB-INF/pages/datavalid/monster_addUI.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>添加妖怪</h3>
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
妖怪年龄~: <form:input path="age"/> <form:errors path="age"/> <br><br>
电子邮件: <form:input path="email"/> <form:errors path="email"/> <br><br>
妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/> 要求以"9999-11-11"的形式<br><br>
妖怪薪水: <form:input path="salary"/> <form:errors path="salary"/> 要求以"123,890.12"的形式<br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
测试
===== map ======
key= monster value=Monster{id=null, email='jack@sohu.com', age=101, name='', birthday=null, salary=null}
key= org.springframework.validation.BindingResult.monster value=org.springframework.validation.BeanPropertyBindingResult: 4 errors
Field error in object 'monster' on field 'birthday': rejected value [20001-15]; codes [typeMismatch.monster.birthday,typeMismatch.birthday,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.birthday,birthday]; arguments []; default message [birthday]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.format.annotation.DateTimeFormat java.util.Date] for value '20001-15'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [20001-15]]
Field error in object 'monster' on field 'salary': rejected value [999x,999]; codes [typeMismatch.monster.salary,typeMismatch.salary,typeMismatch.java.lang.Float,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.salary,salary]; arguments []; default message [salary]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Float' for property 'salary'; nested exception is java.lang.NumberFormatException: For input string: "999x,999"]
Field error in object 'monster' on field 'age': rejected value [101]; codes [Range.monster.age,Range.age,Range.java.lang.Integer,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.age,age]; arguments []; default message [age],100,1]; default message [需要在1和100之间]
Field error in object 'monster' on field 'name': rejected value []; codes [NotEmpty.monster.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.name,name]; arguments []; default message [name]]; default message [不能为空]
===== errors ======
error=Field error in object 'monster' on field 'birthday': rejected value [20001-15]; codes [typeMismatch.monster.birthday,typeMismatch.birthday,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.birthday,birthday]; arguments []; default message [birthday]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.format.annotation.DateTimeFormat java.util.Date] for value '20001-15'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [20001-15]]
error=Field error in object 'monster' on field 'salary': rejected value [999x,999]; codes [typeMismatch.monster.salary,typeMismatch.salary,typeMismatch.java.lang.Float,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.salary,salary]; arguments []; default message [salary]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Float' for property 'salary'; nested exception is java.lang.NumberFormatException: For input string: "999x,999"]
error=Field error in object 'monster' on field 'age': rejected value [101]; codes [Range.monster.age,Range.age,Range.java.lang.Integer,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.age,age]; arguments []; default message [age],100,1]; default message [需要在1和100之间]
error=Field error in object 'monster' on field 'name': rejected value []; codes [NotEmpty.monster.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [monster.name,name]; arguments []; default message [name]]; default message [不能为空]

错误信息太丑了,不是人看的!!
创建 src\i18n.properties
NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
typeMismatch.monster.age=\u5e74\u9f84\u8981\u6c42\u5728\u0031\u002d\u0031\u0035\u0030\u4e4b\u95f4
typeMismatch.monster.birthday=\u751f\u65e5\u683c\u5f0f\u4e0d\u6b63\u786e
typeMismatch.monster.salary=\u85aa\u6c34\u683c\u5f0f\u4e0d\u6b63\u786e
配置国际化文件 springDispatcherServlet-servlet.xml
<!-- 配置国际化错误信息的资源处理bean -->
<bean id="messageSource" class=
"org.springframework.context.support.ResourceBundleMessageSource">
<!-- 配置国际化文件名字
如果你这样配的话,表示messageSource回到 src/i18nXXX.properties去读取错误信息
-->
<property name="basename" value="i18n"></property>
</bean>
完成测试

注意事项和细节说明
- 在需要验证的 Javabean/POJO 的字段上加上相应的验证注解。
- 目标方法上,在 JavaBean/POJO 类型的参数前,添加 @Valid 注解,告知 SpringMVC 该 bean 是需要验证的。
- 在 @Valid 注解之后,添加一个 Errors 或 BindingResult 类型的参数,可以获取到验证的错误信息。
- 需要使用
<form:errors path="email"></form:errors>
标签来显示错误消息,这个标签需要写在<form:form>
标签内生效。 - 错误消息的国际化文件 i18n.properties,中文需要是 Unicode 编码,使用工具转码
格式: 验证规则.表单 modelAttribute 值.属性名=消息信息
NotEmpty.monster.name=\u540D\u5B57\u4E0D\u80FD\u4E3A\u7A7A
typeMismatch.monster.age=\u7C7B\u578B\u4E0D\u5339\u914D
- 注解@NotNull 和 @NotEmpty 的区别说明
(1)@NotEmpty: Asserts that the annotated string, collection, map or array is not {@code null} or empty.
(2)@NotNull:The annotated element must not be {@code null}. Accepts any type.
如果是字符串验证空,建议使用 @NotEmpty。
- SpingMVC 验证时,会根据不同的验证错误,返回对应的信息。
注解的组合使用
问题
age 没有,是空的,提交竟然成功了。

解决问题
修改 Monster.java
private Integer id;
//email是string,使用@NotEmpty
@NotEmpty
private String email;
//@Range(min = 1,max = 100)
//表示接收的age值,在 1-100之间
@NotNull(message = "age不能为空")
@Range(min = 1,max = 100)
private Integer age;
//@NotEmpty 表示name不能为空
//Asserts that the annotated string, collection, map or array is not {@code null} or empty.
@NotEmpty
private String name;
@NotNull(message = "生日不能为空")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NotNull(message = "薪水不能为空")
@NumberFormat(pattern = "###,###.##")
private Float salary;
这样就可以解决上述问题。
数据类型转换校验核心类-DataBinder
图例 Spring MVC 通过反射机制对目标方法进行解析,将请求消息绑定到处理方法的入参中。
数据绑定的核心部件是 DataBinder,运行机制如下

取消某个属性的绑定
说明
在默认情况下,表单提交的数据都会和 pojo 类型的 javabean 属性绑定,如果在开发中,希望取消某个属性的绑定,也就是说,不希望接收到某个表单对应的属性的值,则可以通过 @InitBinder 注解取消绑定。
- 编写一个方法, 使用@InitBinder 标识的该方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定。
- @InitBinder 方法不能有返回值,它必须声明为 void。
- @InitBinder 方法的参数通常是 WebDataBinder。
应用实例
修改 MonsterHandler.java
//取消绑定 monster的name表单提交的值给monster.name属性
@InitBinder
public void initBinder(WebDataBinder webDataBinder){
/**
* 1. 方法上需要标注 @InitBinder springmvc底层会初始化 WebDataBinder
* 2. 调用 webDataBinder.setDisallowedFields("name") 表示取消指定属性的绑定
* 即:当表单提交字段为 name时, 就不在把接收到的name值,填充到model数据monster的name属性
* 3. 机制:springmvc 在底层通过反射调用目标方法时, 接收到http请求的参数和值,使用反射+注解技术
* 取消对指定属性的填充
* 4. setDisallowedFields支持可变参数,可以填写多个字段
* 5. 如果我们取消某个属性绑定,验证就没有意义了,应当把验证的注解去掉, name属性会使用默认值null
* //@NotEmpty
* private String name;
*/
webDataBinder.setDisallowedFields("name");
}
修改 Monster.java
//@NotEmpty 表示name不能为空
//Asserts that the annotated string, collection, map or array is not {@code null} or empty.
//@NotEmpty
private String name;
完成测试
注意事项和细节说明
- setDisallowedFields() 是可变形参,可以指定多个字段。
- 当将一个字段/属性,设置为 disallowed,就不在接收表单提交的值,那么这个字段/属性 的值,就是该对象默认的值 (具体看程序员定义时指定)。
- 一般来说,如果不接收表单字段提交数据,则该对象字段的验证也就没有意义了。