Spring MVC数据校验
一般来说,准许开发中的 DRY 原则,对于Java程序中的数据校验逻辑和相应的域模型会进行绑定,将代码逻辑集中管理;
Spring 在使用DataBinder 对数据进行绑定时,同时可以调用相应的框架进行数据校验工作,提供了 org.springframwork.validation 包用于支持数据校验,其中核心接口为
Validate,支持自己提供的数据校验框架,同时提供一个工厂类
LocaleValidatorFactoryBean 同时支持自身的 Validate 接口、和 JSR-303 的 Validate 接口;
启用 Spring-mvc 数据校验功能
在 spring-mvc 上下文配置中装配一个
LocaleValidatorFactoryBean ,或者直接使用<mvc:annotation-driven> 即可(会自动装配一个 LocaleValidatorFactoryBean):
<mvc:annotation-driven />
相当于以下:
<!--装配验证器工厂-->
<bean id="validatorFactoryBean" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<!--自动扫 Spring mvc 数据校验框架,通知引用指定的验证器工厂-->
<mvc:annotation-driven validator="validatorFactoryBean"/>
数据校验注解
在Spring-mvc中可以直接使用
JSR-303
的校验注解:
@Null / @NotNull | 被注解的元素必须为null / 必须不为null |
@AssertTrue / @AssertFalse | 被注解的元素必须为true / 必须为false |
@Min(value) / @Max(value) | 被注解的元素必须大于等于指定最小值 / 小于等于指定最大值 |
@DecimalMin(value) / @DecimalMax(value) | 同上,同时被注解元素必须为一个数字 |
@Size(max,min) | 被注解的元素必须在指定范围内 |
@Digits(integer,fraction) | 同上,被注解的元素必须是一个数字 |
@Past / @Future | 被注解的元素必须是一个过去的时间 / 必须是一个将来的时间 |
被注解的元素必须是一个Email格式 | |
@Pattern(regexp) | 被注解的元素必须通过正则表达式regexp的验证,该元素必须是一个String |
hibenate validator 对于 JSR-303 提供了一个良好的实现,同时还支持了以下的拓展注解:
@NotEmpty | 被注解的元素必须不为空 |
@Length(min,max) | 被注解的元素长度必须在指定范围内,一般为 String 类型 |
@Range | 被注解的元素必须在核实范围内; |
使用JSR-303 的数据验证注解,需要引入:
javax.validation:validation-api 依赖;
使用 hibernate validator 的数据验证注解,需要导入:org.hibernate:hibernate-validator 依赖;
基于注解的数据校验的基本过程
示例代码模块:
site.assad.domain.User(领域对象)
site.assad.dao.UserDao(dao对象)
site.assad.service.UserService(service对象)
site.assad.web.UserController(控制器对象)
site/assad/applicationContext.xml(spring 配置文件)
webapp/assad-servlet.xml(spring mvc 配置文件)
webapp/web.xml
webapp/WEB-INF/views/user/jsp/*(登陆相关的 jsp 页面)
在领域对象使用注解验证注解
以下是在一个领域对象中,使用注解进行属性的校验注解:
package site.assad.domain;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.*;
public class User {
private int userId;
message="用户名不可为空") (
regexp = "\\S{2,15}",message="用户名必须包含2-15个非空字符") (
private String userName;
message="用户密码不可为空") (
regexp = "\\w{6,30}",message="密码必须包含6-30个有字符,下划线,数字组成的字符") (
private String password;
message="邮箱格式错误") (
private String email;
message="必须是一个过去的日期") (
pattern = "yyyy-MM-dd") //数据格式化 (
private Date birthday;
//省略getter,setter
}
在控制器使用@valid绑定校验模型
在 Spring MVC 控制器的处理方法中,使用
@Valid 让Spring-mvc在数据绑定后进行数据校验,校验的结果会被绑定到
BindingResult 对象中,可以通过该对象获取校验结果的一些信息,常用的方法接口如下:
FieldError getFieldError(String field) | 根据属性名获取对应的校验错误 |
List<FieldError> getFieldErrors() | 获取所有的校验错误 |
Object getFieldValue(String field) | 获取属性值 |
int getErrorCount() | 获取错误数量 |
boolean hasError() | 判断是否含有校验错误 |
//响应 “/register”请求,将数据绑定到“user”模型后,进行数据校验,将校验结果绑定到 BindingResult对象中
"/handleRegister") (
public String register( ("user") User user,BindingResult bindingResult){
if(bindingResult.hasErrors())
return "/user/register";
else
return "/user/showDetail";
}
在JSP页面输出验证错误信息
在JSP中获取校验结果,由于校验错误信息是包含在隐含模型中的,隐含模型又存放在 HttpServletRequest 中,可以使用Spring 提供的
<form> 标签来绑定该模型,通过获取验证结果:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>用户注册</title>
<style> .error{color:darkred;} </style>
</head>
<body>
<jsp:useBean id="user" class="site.assad.domain.User" scope="request" />
<form:form action="/user/handleRegister" method="post" modelAttribute="user">
用户名:<form:input path="userName" /><br/>
<form:errors path="userName" cssClass="error"/><br />
密码:<form:input path="password" /><br/>
<form:errors path="password"/><br />
Email:<form:input path="email" cssClass="error" /><br/>
<form:errors path="email"/><br />
生日(格式"yyyy-MM-dd"):<form:input path="birthday" /><br/>
<form:errors path="birthday" cssClass="error"/><br />
<input type="submit" value="提交" />
<input type="reset" value="重置" /><br/>
</form:form>
</body>
</html>
数据校验信息的国际化
以上示例中,校验的错误信息是硬编码在领域对象中的,为了解决 i18n 问题,可以将这些信息出储存在 i18n 资源中,以下是示例代码模块:
site.assad.domain.User(领域对象)
site.assad.dao.UserDao(dao对象)
site.assad.service.UserService(service对象)
site.assad.web.UserController(控制器对象)
site/assad/applicationContext.xml(spring 配置文件)
webapp/assad-servlet.xml(spring mvc 配置文件)
webapp/i18n/message.properties(i18n 资源文件)
webapp/web.xml
webapp/WEB-INF/views/user/jsp/*(登陆相关的 jsp 页面)
首先创建资源文件 webapp/i18n/message.properties如下:
#标准验证标签信息
register.userName.notNull=用户名必须不为空
register.userName.pattern=用户名必须包含2-15个非空字符
register.password.pattern=密码必须包含6-30个有字符,下划线,数字组成的字符
register.email=邮箱格式错误
register.birthday.past=必须是一个过去的日期
在Spring mvc上下文中装配i18n资源,绑定校验器工厂,assad-servlet.xml
<beans ....>
<!--自动注入 site.assad.web 包的 bean-->
...
<!--配置视图解析器,映射逻辑视图名的解析-->
...
<!--装配验证其信息使用的 i18n 资源-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:defaultEncoding="UTF-8"
p:basename="i18n/message"/>
<!--装配验证器工厂-->
<bean id="validatorFactoryBean" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"
p:validationMessageSource-ref="messageSource" />
<!--自动扫 Spring mvc 数据校验框架,通知引用指定的验证器工厂(装配了i18n资源的验证器工厂)-->
<mvc:annotation-driven validator="validatorFactoryBean"/>
</beans>
更改领域对象中的校验错误信息为相应的 i18n 资源:
public class User {
private int userId;
message="{register.userName.notNull}") (
regexp = "\\S{2,15}",message="{register.userName.pattern}") (
private String userName;
regexp = "\\w{6,30}",message="{register.password.pattern}") (
private String password;
message="{register.email}") (
private String email;
message="{register.birthday.past}") (
pattern = "yyyy-MM-dd") (
private Date birthday;
//省略 getter,setter
}
编写自定义数据校验代码
以上的过程都是基于Spring已经提供的数据校验标签来进行数据校验的,有几种主要的方法:
1)直接在处理方法中进行数据校验
可以直接在控制器处理方法代码块中,获取绑定对象,对绑定对象的属性进行数据校验,再将结果重新绑定到
BindingResult 或 Errors 对象中可以通过以下2种方法绑定校验错误信息:
BindingResult#rejectValue(String field,String errorCode); //向BindingResult对象注入值
ValidationUtils.rejectIfEmptyOrWhitespace(BindingResult bindingResult,String field,String errorCode) //通过校验器工具向 Bindingresult 注入值,同时提供了空值检验的便捷方法
如以下代码对注册表单提交的绑定对象user的userName进行校验,如果数据库中存在该userName,校验失败:
package site.assad.web;
"/user") (
public class UserController {
private UserService userService;
//只负责路由转换
"/register") (
public String register(){
return "/user/jsp/register";
}
//相应表单提交页面的路由请求,进行数据校验;
value="/handleRegister",method=RequestMethod.POST) (
public String register1( ("user") User user, BindingResult bindingResult,Model model){
if(bindingResult.hasErrors()){
return "/user/jsp/register";
}
boolean flag = userService.registerUser(user);
if(flag){
user = userService.getUserByName(user.getUserName());
model.addAttribute("user",user);
return "/user/jsp/userDetail";
}else{
//当userName在数据库中已经存在,返回注册页面,同时向 BindingResult 注入“userName”的校验失败信息
bindingResult.rejectValue("userName","repeated");
return "/user/jsp/register";
}
}
}
其中
bindingResult.rejectValue("userName","repeated"); 即向 BindingResult 对象的 userName 注入一个值,该值的 i18n 错误码为 “repeated”,包括绑定资源文件中的以下键值:
- repeated.user.userName
- repeated.userName
- repeated.java.lang.String
- repeated
BindingResult 会按照以上顺序在对应的资源文件中查找值,直到第一个找到的值;
以下是对应的资源文件:
webapp/i18n/message.properties
#自定义验证信息
repeated.userName=用户名已经存在
同时需要在 Spring mvc上下文中配置 i18n 资源:
<!--装配验证其信息使用的 i18n 资源-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:defaultEncoding="UTF-8"
p:basename="i18n/message"/>
<!--自动扫 Spring mvc 数据校验/数据转化/数据格式化框架-->
<mvc:annotation-driven />
2)通过自定义实现的校验器
以上方式是直接在处理方法中编写校验逻辑,但对于某个command对象含有比较复杂的校验逻辑,可以把这些代码抽取出来,通过继承Validator接口,实现该command对象的自定义校验器,以维持代码的分层;
实现 User 对象的校验器UserValidator:
package site.assad.web.validator;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
//针对 User 的校验器
public class UserValidator implements Validator {
private UserService userService;
//判断需要需要校验的类
public boolean supports(Class<?> clazz) {
return User.class.equals(clazz);
}
//校验逻辑
public void validate(Object target, Errors errors) {
if (target instanceof User) {
User user = (User) target;
if (userService.getUserByName(user).getUserId != 0) {
errors.rejectValue("userName", "repeated"); //向错误对象中写入i18n资源中对应的错误码,同上
}
}
}
}
控制器中 UserController 中绑定该自定义校验器
public class UserController{
public void initBinder(WebDataBinder binder){
binder.setValidator(new UserValidator()); //在进行数据绑定是使用自定义校验器
}
"/handleRegister") (
public String register( ("user") User user,BindingResult bindingResult,Model model){
if(bindingResult.hasErrors()){
return "/user/register";
}else{
user = userService.getUserByName(user.getUserName());
model.addAttribute("user",user);
return "/user/showDetail";
}
}
}
以上仅仅是在 UserController 控制器中局部启用自定义校验器 UserValidator,如果要在全局启用该校验器,可以在spring mvc上下文如如下配置:
<bean id="userValidator" class="site.assad.validator.UserValidator"/>
<mvc:annotation-driven validator="userValidator"/>