最近在学习Spring3.1,基于注解的MVC, 使用过程中发现,开始的时候很不喜欢那个JSR 303 推荐的校验机制,原因很简单就是要记很多Annotation, 每个校验类型一个annotation,而且也不喜欢那个正则式的校验方式,还是喜欢独立的一个校验方法的方式,像以前的struts里的那个对form的校验,或者spring里的校验接口那种的。但是找了老半天,发现spring3.1没有提供太多的学习资料和使用介绍。
自己在学习API和reference document的基础上,找到了这两种校验方式。在此总结下,供大家参考。
Spring 基于注释的校验机制:
1) 支持Spring框架定义的Validator接口定义的校验。
2)支持JSR303 Bean Validation定义的校验规范。
下面使用一个用户注册的例子来就二者的使用进行讲解。
平台: Spring 3.1.0
用户注册页面: 名称:user.jsp
注册用户包含三项信息: 用户名,密码,邮箱。
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>User Register Page</title>
<style type="text/css">
.error{
color: red;
}
</style>
</head>
<body>
<%--
这里指定页面绑定的对象 modelAttribute. 之前很困惑,
为什么<form>上最重要的 <form action="someAction.do">属性没了呢?
后来发现,其实在controller中的方法以及指定了地址到method的对应关系,
<form>里的action属性就可以退休了。
--%>
<sf:form method="post" modelAttribute="user">
<p>用户注册页面:</p>
<table width="60%" align="center">
<colgroup>
<col width="10%" align="right" />
<col />
</colgroup>
<tr>
<th>用户名:</th>
<td>
<sf:input path="userName" />
<small>length of userName is not more than 20.</small><br />
<%-- 显示关于userName属性相关的错误信息。 --%>
<sf:errors path="userName" cssClass="error"/>
</td>
</tr>
<tr>
<th>密码:</th>
<td>
<sf:password path="password" />
<small>length of password is not less than 6.</small><br />
<sf:errors path="password" cssClass="error" />
</td>
</tr>
<tr>
<th>邮箱:</th>
<td>
<sf:input path="email"/>
<small>format should confirm to general standard.</small><br />
<sf:errors path="email" cssClass="error" />
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="注册" />
</td>
</tr>
</table>
</sf:form>
</body>
</html>
这里没有使用前段的JavaScript校验,目的是为了了解和熟悉使用后台的校验机制。
与前台页面相关联的对象: User, 包含userName, password, email等属性。
public class User {
private String userName;
private String password;
private String email;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(getClass()).append("[")
.append("userName=").append(userName).append(", ")
.append("password=").append(password).append(", ")
.append("email=").append(email).append("]");
return sb.toString();
}
}
本人比较偏好Spring框架的Validator方式校验,所以先说第一种方式。
校验方式一: Spring Validator
1)Validator接口的实现:
Spring框架的Validator接口定义,
package org.springframework.validation;
public interface Validator {
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}
要使用它进行校验必须实现该接口。实现Validator接口的代码如下:
package org.study.validation.validator;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.study.domain.User;
public class UserValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(User.class);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "userName", "user.userName.required", "用户名不能为空");
ValidationUtils.rejectIfEmpty(errors, "password", "user.password.required", "密码不能为空");
ValidationUtils.rejectIfEmpty(errors, "email", "user.email.required", "邮箱不能为空");
User user = (User)target;
int length = user.getUserName().length();
if(length>20){
errors.rejectValue("userName", "user.userName.too_long", "用户名不能超过{20}个字符");
}
length = user.getPassword().length();
if(length <6){
errors.rejectValue("password", "user.password.too_short", "密码太短,不能少于{6}个字符");
}else if(length>20){
errors.rejectValue("password", "user.password.too_long", "密码太长,不能长于{20}个字符");
}
int index = user.getEmail().indexOf("@");
if(index == -1){
errors.rejectValue("email", "user.email.invalid_email", "邮箱格式错误");
}
}
}
2) 设置Validator,并触发校验。
在我们的Controller中需要使用父类已有的方法来为DataBinder对象指定Validator, protected initBinder(WebDataBinder binder); 代码如下:
@InitBinder
protected void initBinder(WebDataBinder binder){
binder.setValidator(new UserValidator());
}
为binder对象指定好Validator校验对象后,下面一步的就是在需要校验的时候触发validate方法,该触发步骤是通过 @Validated 注解(该注解是Spring框架定义的)实现的。
/**
* 处理提交的用户注册信息。
* @param model
* @return
*/
@RequestMapping (method = RequestMethod.POST)
public String doRegister(@Validated User user, BindingResult result){
if(logger.isDebugEnabled()){
logger.debug("process url[/user], method[post] in "+getClass());
}
//校验没有通过
if(result.hasErrors()){
return "user";
}
if(user != null){
userService.saveUser(user);
}
return "user";
}
至此,从页面提交的User对象可以通过我们实现的UserValidator类来校验了,校验的结果信息存入BindingResult result对象中。在前台页面可以使用spring-form的标签<sf:errors path="*" />来显示。
校验方式二: JSR303 Bean Validation
在Spring3.1中增加的了对JSR303 Bean Validation规范的支持,不仅可以对Spring的 MVC进行校验,而且也可以对Hibernate的存储对象进行校验。是一个通用的校验框架。
环境准备:
A) 导入Hibernate-Validator
要使用JSR303 校验框架, 需要加入框架的具体实现Hibernate-Validator, 在soureforge上下载最新的Hibernate-Validator, 当前版本为4.2.0 Final版。
在/WEB-INF/lib中导入 hibernate-validator-4.2.0.Final.jar, hibernate-validator-annotation-processor-4.2.0.Final.jar, 导入它的lib/required目录下内容slf4j-api-1.6.1.jar, validation-api-1.0.0.GA.jar;
B) 配置Spring对JSR 303 的支持。
在你的 <servletName>-servlet.xml配置文件中,使用标签:
<mvc:annotation-driven />
配置对JSR303的支持,包括制动查找Hibernate-Validator的实现等工作。
1) 校验属性的Constraints的设定。
该步骤就是对要校验的对象的属性,使用已经定义的Constraints对需要校验的属性进行约束。在JSR303中已经定义的Constraint如下:
表 1. Bean Validation 规范内嵌的约束注解定义
约束注解名称 约束注解说明
@Null 验证对象是否为空
@NotNull 验证对象是否为非空
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMin 验证 Number 和 String 对象是否大等于指定的值,小数存在精度
@DecimalMax 验证 Number 和 String 对象是否小等于指定的值,小数存在精度
@Size 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Digits 验证 Number 和 String 的构成是否合法
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
通过上述Constraint约束后的User对象如下:
package org.study.domain;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.study.validation.annotation.NotEmpty;
public class User {
@Size (min=3, max=20, message="用户名长度只能在3-20之间")
private String userName;
@Size (min=6, max=20, message="密码长度只能在6-20之间")
private String password;
@Pattern (regexp="[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}", message="邮件格式错误")
private String email;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(getClass()).append("[")
.append("userName=").append(userName).append(", ")
.append("password=").append(password).append(", ")
.append("email=").append(email).append("]");
return sb.toString();
}
}
2) Validate的触发
在需要校验的对象前增加 @Valid 注解 (该注解位于javax.validation包中)来触发校验。示例如下:
/**
* 处理提交的用户注册信息。
* @param model
* @return
*/
@RequestMapping (method = RequestMethod.POST)
public String doRegister(@Valid User user, BindingResult result){
if(logger.isDebugEnabled()){
logger.debug("process url[/user], method[post] in "+getClass());
}
//校验没有通过
if(result.hasErrors()){
return "user";
}
if(user != null){
userService.saveUser(user);
}
return "user";
}
这样就可以完成针对输入数据User对象的校验了, 校验结果任然保存在BindingResult对象中。
注: 想要往页面传数据的时候,不要调用result.getModel().put(key, value); 这样做将不起作用,因为getModel()方法每次都是生成一个新的,保存的东西就会丢失。
最好在响应方法中加一个: Map<String, Object> map来保存数据。
最后对参考文献中作者们的贡献表示感谢。
参考文献: