9.1 数据校验
应用通过输入页面收集的数据是非常复杂的,不仅会包含正常用户的误输入,还可能包含恶意用户的恶意输入。异常的输入,轻则会导致系统非正常中断,重则会导致系统崩溃。应用程序必须能正常处理表现层接收的各种数据,通常的做法是遇到异常输入时应用程序直接返回,提示用户必须重新输入,也就是将哪些异常输入过滤掉。这种对异常输入的过滤,就是输入校验,也称为“数据校验”。一个健壮的应用系统必须将这些非法输入阻止在应用外,防止这些非法输入进入系统,这样才能保证系统不受影响。
输入校验分为客户端校验和服务器端校验,客户端校验主要是过滤非正常用户的误操作,通常通过 JavaScript 代码完成;服务器端校验是整个应用阻止非法数据的最后防线,主要通过在应用中编程实现。
客户端校验的主要作用是防止用户的误输入,这仅能对输入进行初步过滤;对于恶意用户的恶意行为,客户端校验无能为力。因此,客户端校验绝不可替代服务器校验。当然客户端校验也绝不可少,因为 Web 应用的大部分用户是正常用户,他们的输入可能包含了大量的误输入,客户端校验能把这些误输入阻止在客户端,从而降低了服务器的负载。
Spring MVC 提供了强大的数据校验功能,其中有两种方法可以验证输入:一种是利用 Spring 自带的 Validation 校验框架;另一种是利用 JSR 303 (Java 验证规范)实现校验功能。
9.1.1 Spring 的 Validation 校验框架
Spring 拥有自己独立的数据校验框架。Spring 在进行数据绑定时,可同时调用校验框架来完成数据校验工作。
Spring 的校验框架在 org.springframework.validation 包中,其中重要的接口和类如下:
- Validation。最重要的接口。
- Errors。Spring 用来存放错误信息的接口。
- ValidationUtils。Spring 提供的一个关于校验的工具类。
- LocalValidationFactoryBean。位于 org.springframework.validation.beanvalidation 包中,该类既实现了 Spring 的 Validation 接口,也实现了 JSR 303 的 Validation 接口。只要在 Spring 容器定义一个 LocalValidationFactorybean,即可将其注入到需要数据校验的 Bean 中。定义一个 LocalValidationFactoryBean 的 Bean 非常简单:
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<mvc:annotation-driven/>
会默认装配好一个 LocalValidationFactoryBean,所以在实际开发中不需要手动配置 LocalValidationFactoryBean。需要注意的是:Spring 本身没有提供 JSR 303 的实现,如果要使用 JSR 303 完成验证,则必须将 JSR 303 的实现(注入 Hibernate Validator)jar 文件加入到应用程序的类路径下,这样 Spring 会自动加载并装配好 JSR 303 的实现。
示例:Spring 的 Validation 校验
- 新建 loginForm.jsp,页面使用
<form:errors>
标签显示属性的错误信息。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试 Validation 接口验证</title>
</head>
<body>
<jsp:useBean id="user" class="domain.User" scope="request"/>
<form:form modelAttribute="user" method="post" action="login">
<table>
<tr>
<td><label>登录名:</label></td>
<td><form:input path="loginname"/></td>
<!-- 显示 loginname 属性的错误信息 -->
<td><form:errors path="loginname" cssStyle="color:red"/></td>
</tr>
<tr>
<td><label>密码:</label></td>
<td><form:input path="password"/></td>
<!-- 显示 password 属性的错误信息 -->
<td><form:errors path="password" cssStyle="color:red"/> </td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form:form>
</body>
</html>
- 新建 User 类负责接收页面输入信息。
package domain;
import java.io.Serializable;
public class User implements Serializable {
private String loginname;
private String password;
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"loginname='" + loginname + '\'' +
", password='" + password + '\'' +
'}';
}
}
- 新建 UserValidator 类。
package validator;
import domain.User;
import org.springframework.stereotype.Repository;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
// 实现 Spring 的 Validator 接口
@Repository("userValidator")
public class UserValidator implements Validator {
// 从该校验器能够对 clazz 类型的对象进行校验。
@Override
public boolean supports(Class<?> aClass) {
// User 指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。
return User.class.isAssignableFrom(aClass);
}
// 对目标类 target 进行校验,并将校验错误记录在 errors 当中。
@Override
public void validate(Object target, Errors errors) {
/*
* 使用 ValidationUtils 中的一个静态方法rejectIfEmpty() 来对 loginname 属性进行数据校验,
* 假若 'loginname' 属性是 null 或者空字符串的话,就拒绝验证通过。
*/
ValidationUtils.rejectIfEmpty(errors,"loginname",null,"登录名不能为空!");
ValidationUtils.rejectIfEmpty(errors,"password",null,"密码不能为空!");
User user = (User)target;
if (user.getLoginname().length() > 10){
// 使用 Errors 的 rejectValue 方法验证
errors.rejectValue("loginname",null,"用户名不能超过十个字符!");
}
if (user.getPassword() != null
&& !user.getPassword().equals("")
&& user.getPassword().length() < 6){
errors.rejectValue("password",null,"密码不能小于6位!");
}
}
}
UserValidator 实现了 Spring 的 Validator 接口,其可以对 User 对象进行数据校验,并分别使用 ValidationUtils 的 rejectIfEmpty 方法和 Errors 的 rejectValue 方法对 User 进行了数据校验。@Repository(“userValidator”)注解将该对象注释为 Spring 容器中的一个 Bean,名字为 “userValidator”。
- 新建 UserController 类控制页面跳转。
package controller;
import domain.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import validator.UserValidator;
@Controller
public class UserController {
private final static Log logger = LogFactory.getLog(UserController.class);
@RequestMapping(value = "/{formName}")
public String loginForm(@PathVariable String formName){
// 动态跳转页面
return formName;
}
// 注入 UserValidator 对象
@Autowired
@Qualifier(value = "userValidator")
private UserValidator userValidator;
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(
@ModelAttribute User user,
Model model,
Errors errors
){
logger.info(user);
model.addAttribute("user",user);
// 调用 userValidator 的验证方法
userValidator.validate(user,errors);
// 如果验证不通过跳转到 loginForm 视图
if (errors.hasErrors()){
return "loginForm";
}
return "success";
}
}
login 方法将对传进来的参数进行校验,注意方法的最后一个参数 errors,该参数是一个 Spring 校验框架的 Errors 对象。在该方法中调用了之前写的 userValidator 类进行数据校验,如果校验失败,则跳转到“loginForm”视图。
- 新建 success.jsp 页面。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>成功</title>
</head>
<body>
<form:form modelAttribute="user" method="post" action="">
<table>
<tr>
<td><label>用户名:</label></td>
<td><form:input path="loginname"/></td>
</tr>
<tr>
<td><label>密码:</label></td>
<td><form:input path="password"/></td>
</tr>
</table>
</form:form>
</body>
</html>
- 修改 Spring MVC 控制文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="controller"/>
<!-- 装配自定义的类型转换器 -->
<mvc:annotation-driven/>
<!-- 使用默认的 Servlet 来响应静态文件 -->
<mvc:default-servlet-handler/>
<!-- 视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix">
<value>/WEB-INF/content/</value>
</property>
<!-- 后缀 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
- 修改 Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="validator"/>
</beans>
- 修改 web.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 部署 ValidatorTest 应用,在浏览器中输入如下 URL 测试应用:
http://localhost:8080/ValidatorTest/loginForm
不输入登录名和密码直接提交,结果如下所示:
输入不合法的登录名和密码,提交后结果如下所示:
可以看到,校验框架校验后返回了错误信息并将其显示在页面上。
由于早期 Spring 就被设计了 Validation 框架,所以之前的很多应用都使用 Validation 框架进行校验。由于 Validation 框架通过硬编码完成数据校验,在实际开发中会显得比较麻烦,因此现代开发更加推荐使用 JSR 303 完成数据校验。
注: 起初运行程序时,程序报错为:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
翻译为:
java.lang.IllegalStateException:BindingResult和bean名“user”的纯目标对象都不可用作请求属性
分析后发现,报错位置应在<form:form modelAttribute="user" method="post" action="login">
,在 loginForm.jsp 页面调用 user 时 user 并未被实例化,因此,页面无法调用。
解决办法:
在 loginForm.jsp 页面中加入如下代码:
<jsp:useBean id="user" class="domain.User" scope="request"/>
9.1.2 JSR 303 校验
JSR 303 是 Java 为 Bean 数据合法性校验所提供的一个标准规范,叫做 Bean Validation。官方参考实现是 Hibernate Validator。
Bean Validation 为 JavaBean 验证定义了相应的元数据类型和 API。在应用程序中,通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标注的验证接口对 Bean 进行验证。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
JSR 303 是一个规范,它的核心接口是 javax.validation.Validator,该接口根据目标对象中所标注的校验注解进行数据校验,并得到校验结果。JSR 303 目前有两个实现,一个是 Hibernate Validator,另一个是 Apache bval。
JSR 303 中定义了一套可标注在成员变量、属性方法上的校验注解。
注解 | 功能 | 范例 |
---|---|---|
@Null | 验证对象是否为 null。 | @Null String desc; |
@NotNull | 验证对象是否不为 null,无法检查长度为 0 的字符串,用于验证基本数据类型。 | @NotNull String name; |
@AssertTrue | 验证 Boolean 对象是否为 true。 | @AssertTrue boolean isEmpty; |
@Max(value) | 验证 Number 和 String 对象是否小于等于指定的值。 | @Max(18) int age; |
@Min(value) | 验证 Number 和 String 对象是否大于等于指定的值。 | @Min(10) int age; |
@DecimalMax(value) | 被标注的值必须不大于约束中指定的最大值。这个约束的参数是一个通过 BigDecimal 定义的最大值的字符串表示,小数存在精度。 | @DecimalMax(1.1) BigDecimal price; |
@DecimalMin(value) | 被标注的值必须不小于约束中指定的最小值。这个约束的参数是一个通过 BigDecimal 定义的最大值的字符串表示,小数存在精度。 | @DecimalMin(0.5) BigDecimal Price; |
@Digits(integer,fraction) | 验证字符串是否是符合指定格式的数字,integer 指定整数精度,fraction 指定小数精度。 | @Digits(integer=5,fraction=2) BigDecimal price; |
@Size(min,max) | 验证对象(Array、Collection、Map、String)长度是否在给定的范围之内。 | @Size(min=15,max=60) int age; |
@Past | 验证 Date 和 Callendar 对象是否在当前时间之前。 | @Past Date birthDate; |
@Future | 验证 Date 和 Callendar 对象是否在当前时间之后。 | @Future Date ShippingDate; |
@Pattern | 验证 String 对象是否符合正则表达式的规则。 | @Pattern(regexp="[1][3,8][3,6,9][0-9]{8}") String phone; |
Hibernate Validator 是 JSR 303 的一个参考实现,除了支持所有标准的校验注解之外,它还扩展了如下注解:
注解 | 功能 | 范例 |
---|---|---|
@NotBlank | 检查约束字符串是不是 Null,被 Trim 的长度是否大于 0。只对字符串,且会去掉前后空格。 | @NotBlank String name; |
@URL | 验证是否是合法的 url。 | @URL String url; |
验证是否是合法的邮件地址。 | @Email String email; |
|
@CreditCardNumber | 验证是否是合法的信用卡号码。 | @CreditCardNumber String creditCard; |
@Length(min,max) | 验证字符串的长度必须在指定的范围内。 | @Length(min=6,max=8) String password; |
@NotEmpty | 检查元素是否为 NULL 或者 EMPTY。用于 Array、Collection、Map、String。 | @NotEmpty String name; |
@Range(min,max,message) | 验证属性值必须在合适的范围内。 | @Range(min=18,max=60,message=“学生的年龄必须在 18 岁到 60 岁之间”) int age; |
示例:测试 JSR 303 校验
本例使用的是 Hibernate Validator 的实现,创建 JSR303Test 工程后,将 hibernate-validator-5.2.4.Final.jar、hibernate-validator-annotation-processor-5.2.4.Final.jar、hibernate-validator-cdi-5.2.4.Final.jar、validation-api-1.1.0.Final.jar、classmate-1.1.0.jar 和 jboss-logging-3.2.1.Final.jar 加入项目当中。
- 新建 registerForm.jsp 注册页面,用于提交用户注册信息,注册信息包括用户名、密码、邮箱、电话等。之后将在后台使用 JSR 303 进行验证。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试 JSR 303</title>
</head>
<body>
<h3>注册页面</h3>
<form:form modelAttribute="user" method="post" action="login">
<table>
<tr>
<td>登录名:</td>
<td><form:input path="loginname"/></td>
<td><form:errors path="loginname" cssStyle="color:red"/></td>
</tr>
<tr>
<td>密码:</td>
<td><form:input path="password"/></td>
<td><form:errors path="password" cssStyle="color:red"/></td>
</tr>
<tr>
<td>用户名:</td>
<td><form:input path="username"/></td>
<td><form:errors path="username" cssStyle="color:red"/></td>
</tr>
<tr>
<td>年龄:</td>
<td><form:input path="age"/></td>
<td><form:errors path="age" cssStyle="color:red"/></td>
</tr>
<tr>
<td>邮箱:</td>
<td><form:input path="email"/></td>
<td><form:errors path="email" cssStyle="color:red"/></td>
</tr>
<tr>
<td>生日:</td>
<td><form:input path="birthday"/></td>
<td><form:errors path="birthday" cssStyle="color:red"/></td>
</tr>
<tr>
<td>电话:</td>
<td><form:input path="phone"/></td>
<td><form:errors path="phone" cssStyle="color:red"/></td>
</tr>
<tr>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form:form>
</body>
</html>
- 新建 User 类用于接收用户输入信息,User 类使用了 Hibernate Validator 的注解对前台提交的数据进行验证。
package domain;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
@NotBlank(message="登录名不能为空")
private String loginname;
@NotBlank(message = "密码不能为空")
@Length(min = 6,max = 8,message = "密码长度必须在 6 位到 8 位之间")
private String password;
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "年龄不能为空")
@Range(min = 15,max = 60,message = "年龄必须在15岁到60岁之间")
private Integer age;
@Email(message = "必须是合法的邮箱地址")
private String email;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past(message = "生日必须是一个过去的日期")
private Date birthday;
@Pattern(regexp = "[1][3,8][3,6,9][0-9]{8}",message = "无效的电话号码")
private String phone;
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
- 新建 UserController 类控制页面跳转。UserController中使用 @Valid 注解对提交的数据进行校验,后面跟着 Errors 对象保存校验信息。如果 errors 中有错误信息,则返回 registerForm.jsp 页面,验证通过则跳转到 success.jsp 页面。
package controller;
import domain.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.validation.Valid;
@Controller
public class UserController {
private final static Log logger = LogFactory.getLog(UserController.class);
@RequestMapping(value = "/{formName}")
public String loginForm(@PathVariable String formName, Model model){
User user = new User();
model.addAttribute("user",user);
// 动态跳转页面。
return formName;
}
// 数据校验使用 @Valid,后面跟着 Errors 对象保存校验信息。
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(
@Valid @ModelAttribute User user,
Errors errors,
Model model){
logger.info(user);
if (errors.hasErrors()){
return "registerForm";
}
model.addAttribute("user",user);
return "success";
}
}
- 新建 success.jsp 页面,用于显示数据校验通过的结果。
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试 JSR 303</title>
</head>
<body>
<h3>测试 JSR 303</h3>
登录名:${requestScope.user.loginname}<br>
密码:${requestScope.user.password}<br>
用户名:${requestScope.user.username}<br>
年龄:${requestScope.user.age}<br>
邮箱:${requestScope.user.email}<br>
生日:<fmt:formatDate value="${requestScope.user.birthday}" pattern="yyyy-MM-dd"/><br>
电话:${requestScope.user.phone}<br>
</body>
</html>
- 修改 Spring MVC 配置文件。由于
<mvc:annotation-driven/>
会默认装配好一个 LocalValidatorFactoryBean,因此 Spring MVC 配置文件不需增加额外的配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="controller"/>
<!-- 装配自定义的类型转换器 -->
<mvc:annotation-driven/>
<!-- 使用默认的 Servlet 来响应静态文件 -->
<mvc:default-servlet-handler/>
<!-- 视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix">
<value>/WEB-INF/content/</value>
</property>
<!-- 后缀 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
- 修改 web.xml 文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 部署 JSR303Test 应用,在浏览器中输入如下 URL:
http://localhost:8080/JSR303Test/registerForm
结果如下图所示:
输入错误信息,直接单击提交,后台验证不通过,如下图所示:
输入符合校验规则的注册信息,通过验证后会跳转到 success 页面。如下图所示:
以上使用注解的 message 属性输出错误信息,而在实际项目中,我们希望错误信息更加人性化、更具可读性,同时还希望显示国际化的错误信息。
Spring MVC 支持国际化显示数据校验的错误信息。每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象,FieldError 对象实现了 org.springframework.context.MessageSourceResolvable 接口,顾名思义,MessageSourceResolvable 是可用国际化资源进行解析的对象。MessageSourceResolvable 接口有如下3个方法:
- Object[] getArguments()。返回一组参数对象。
- String[] getCodes()。返回一组消息代码,每一个代码对应一个属性资源,可以使用 getArguments() 返回的参数对资源属性进行参数替换。
- String getDefaultMessage()。默认的消息,如果没有装配相应的国际化资源,那么显示的所有错误信息都是默认的。
当一个属性校验失败后,校验框架会为该属性生成4个消息代码,这些代码以校验注解类名为前缀,结合类名、属性名以及属性类型名生成多个对应的消息代码。
当使用 Spring MVC 标签显示错误信息时,Spring MVC 会查看 Web 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误信息,否则使用国际化消息对错误代码进行显示。
知道错误对象的错误码是对应国际化消息的键名称后,定义两个国际化资源文件,在国际化资源文件中为错误代码定义相应的本地化消息内容。
文件名为:message_en_US.properties
NotBlank.user.loginname= 登录名不能为空
NotBlank.user.password= 密码不能为空
Length.user.password= 密码长度必须在6位到8位之间
NotBlank.user.username= 用户名不能为空
Range.user.age= 年龄必须在15岁到60岁之间
Email.user.email= 必须是合法的邮箱地址
Past.user.birthday= 生日必须是一个过去的日期
Pattern.user.phone= 无效的电话号码
文件名为:message_zh_CN.properties
NotBlank.user.loginname = Loginname is not null
NotBlank.user.password = Password is not null
Length.user.password = Password length must be between 6 and 8
NotBlank.user.username = Username is not null
Range.user.age = Age must be between the ages of 15 to 60
Email.user.email = Must be a legitimate email address
Past.user.birthday = Birthday must be a date in the past
Pattern.user.phone = Invalid phone number
接下来在 Spring MVC 配置文件中增加国际化配置。
通过该选项创建属性文件。然后在 Spring MVC 配置文件中增加国际化配置。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<!-- 国际化资源文件 -->
<property name="basename" value="message"/>
</bean>