最近在学习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>
与前台页面相关联的对象: 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 />
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来保存数据。
最后对参考文献中作者们的贡献表示感谢。
参考文献: