假设我们现在有一个需要进行验证的实体类User,其代码如下所示:
- public class User {
- private String username;
- private String password;
- 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 toString() {
- return username + ", " + password;
- }
- }
那么当我们需要使用SpringMVC提供的Validator接口来对该实体类进行校验的时候该如何做呢?这个时候我们应该提供一个Validator的实现类,并实现Validator接口的supports方法和validate方法。Supports方法用于判断当前的Validator实现类是否支持校验当前需要校验的实体类,只有当supports方法的返回结果为true的时候,该Validator接口实现类的validate方法才会被调用来对当前需要校验的实体类进行校验。这里假设我们需要验证User类的username和password都不能为空,先给出其代码,稍后再进行解释。这里我们定义一个UserValidator,其代码如下:
- import org.springframework.validation.Errors;
- import org.springframework.validation.ValidationUtils;
- import org.springframework.validation.Validator;
- public class UserValidator implements Validator {
- public boolean supports(Class<?> clazz) {
- // TODO Auto-generated method stub
- return User.class.equals(clazz);
- }
- public void validate(Object obj, Errors errors) {
- // TODO Auto-generated method stub
- ValidationUtils.rejectIfEmpty(errors, "username", null, "Username is empty.");
- User user = (User) obj;
- if (null == user.getPassword() || "".equals(user.getPassword()))
- errors.rejectValue("password", null, "Password is empty.");
- }
- }
在上述代码中我们在supports方法中定义了该UserValidator只支持对User对象进行校验。在validate方法中我们校验了User对象的username和password不为empty的情况,这里的empty包括null和空字符串两种情况。ValidationUtils类是Spring中提供的一个工具类。Errors就是Spring用来存放错误信息的对象。
我们已经定义了一个对User类进行校验的UserValidator了,但是这个时候UserValidator还不能对User对象进行校验,因为我们还没有告诉Spring应该使用UserValidator来校验User对象。在SpringMVC中我们可以使用DataBinder来设定当前Controller需要使用的Validator。先来看下面一段代码:
- import javax.validation.Valid;
- import org.springframework.stereotype.Controller;
- import org.springframework.validation.BindingResult;
- import org.springframework.validation.DataBinder;
- import org.springframework.web.bind.annotation.InitBinder;
- import org.springframework.web.bind.annotation.RequestMapping;
- @Controller
- public class UserController {
- @InitBinder
- public void initBinder(DataBinder binder) {
- binder.setValidator(new UserValidator());
- }
- @RequestMapping("login")
- public String login(@Valid User user, BindingResult result) {
- if (result.hasErrors())
- return "redirect:user/login";
- return "redirect:/";
- }
- }
在上面这段代码中我们可以看到我们定义了一个UserController,该Controller有一个处理login操作的处理器方法login,它需要接收客户端发送的一个User对象,我们就是要利用前面的UserValidator对该User对象进行校验。首先我们可以看到我们login方法接收的参数user是用@Valid进行标注的,这里的@Valid是定义在JSR-303标准中的,我这里使用的是Hibernate Validation对它的实现。这里我们必须使用@Valid标注我们需要校验的参数user,否则Spring不会对它进行校验。另外我们的处理器方法必须给定包含Errors的参数,这可以是Errors本身,也可以是它的子类BindingResult,使用了Errors参数就是告诉Spring关于表单对象数据校验的错误将由我们自己来处理,否则Spring会直接抛出异常,而且这个参数是必须紧挨着@Valid参数的,即必须紧挨着需要校验的参数,这就意味着我们有多少个@Valid参数就需要有多少个对应的Errors参数,它们是一一对应的。前面有提到我们可以通过DataBinder来指定需要使用的Validator,我们可以看到在上面代码中我们通过@InitBinder标记的方法initBinder设置了当前Controller需要使用的Validator是UserValidator。这样当我们请求处理器方法login时就会使用DataBinder设定的UserValidator来校验当前的表单对象User,首先会通过UserValidator的supports方法判断其是否支持User对象的校验,若支持则调用UserValidator的validate方法,并把相关的校验信息存放到当前的Errors对象中。接着我们就可以在我们的处理器方法中根据是否有校验异常信息来做不同的操作。在上面代码中我们定义了在有异常信息的时候就跳转到登陆页面。这样我们就可以在登陆页面上通过errors标签来展示这些错误信息了。
我们知道在Controller类中通过@InitBinder标记的方法只有在请求当前Controller的时候才会被执行,所以其中定义的Validator也只能在当前Controller中使用,如果我们希望一个Validator对所有的Controller都起作用的话,我们可以通过WebBindingInitializer的initBinder方法来设定了。另外,在SpringMVC的配置文件中通过mvc:annotation-driven的validator属性也可以指定全局的Validator。代码如下所示:
- <?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-3.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd
- http://www.springframework.org/schema/mvc
- http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
- <mvc:annotation-driven validator="userValidator"/>
- <bean id="userValidator" class="com.xxx.xxx.UserValidator"/>
- ...
- </beans>
---------------------------------------------------------------------------------------------------------------------------------------
我们采用Hibernate-validator来进行验证,Hibernate-validator实现了JSR-303验证框架支持注解风格的验证。首先我们要到http://hibernate.org/validator/下载需要的jar包,这里以4.3.1.Final作为演示,解压后把hibernate-validator-4.3.1.Final.jar、jboss-logging-3.1.0.jar、validation-api-1.0.0.GA.jar这三个包添加到项目中。
配置之前项目中的springservlet-config.xml文件,如下:
<!-- 默认的注解映射的支持 --> <mvc:annotation-driven validator="validator" conversion-service="conversion-service" /> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> <!--不设置则默认为classpath下的 ValidationMessages.properties --> <property name="validationMessageSource" ref="validatemessageSource"/> </bean> <bean id="conversion-service" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" /> <bean id="validatemessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:validatemessages"/> <property name="fileEncodings" value="utf-8"/> <property name="cacheSeconds" value="120"/> </bean>
其中<property name="basename" value="classpath:validatemessages"/>中的classpath:validatemessages为注解验证消息所在的文件,需要我们在resources文件夹下添加。
在com.demo.web.controllers包中添加一个ValidateController.java内容如下:
package com.demo.web.controllers; import java.security.NoSuchAlgorithmException; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.demo.web.models.ValidateModel; @Controller @RequestMapping(value = "/validate") public class ValidateController { @RequestMapping(value="/test", method = {RequestMethod.GET}) public String test(Model model){ if(!model.containsAttribute("contentModel")){ model.addAttribute("contentModel", new ValidateModel()); } return "validatetest"; } @RequestMapping(value="/test", method = {RequestMethod.POST}) public String test(Model model, @Valid @ModelAttribute("contentModel") ValidateModel validateModel, BindingResult result) throws NoSuchAlgorithmException{ //如果有验证错误 返回到form页面 if(result.hasErrors()) return test(model); return "validatesuccess"; } }
其中@Valid @ModelAttribute("contentModel") ValidateModel validateModel的@Valid 意思是在把数据绑定到@ModelAttribute("contentModel") 后就进行验证。
在com.demo.web.models包中添加一个ValidateModel.java内容如下:
package com.demo.web.models; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; public class ValidateModel{ @NotEmpty(message="{name.not.empty}") private String name; @Range(min=0, max=150,message="{age.not.inrange}") private String age; @NotEmpty(message="{email.not.empty}") @Email(message="{email.not.correct}") private String email; public void setName(String name){ this.name=name; } public void setAge(String age){ this.age=age; } public void setEmail(String email){ this.email=email; } public String getName(){ return this.name; } public String getAge(){ return this.age; } public String getEmail(){ return this.email; } }
在注解验证消息所在的文件即validatemessages.properties文件中添加以下内容:
name.not.empty=\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u3002 age.not.inrange=\u5E74\u9F84\u8D85\u51FA\u8303\u56F4\u3002 email.not.correct=\u90AE\u7BB1\u5730\u5740\u4E0D\u6B63\u786E\u3002 email.not.empty=\u7535\u5B50\u90AE\u4EF6\u4E0D\u80FD\u60DF\u6050\u3002
其中name.not.empty等分别对应了ValidateModel.java文件中message=”xxx”中的xxx名称,后面的内容是在输入中文是自动转换的ASCII编码,当然你也可以直接把xxx写成提示内容,而不用另建一个validatemessages.properties文件再添加,但这是不正确的做法,因为这样硬编码的话就没有办法进行国际化了。
在views文件夹中添加validatetest.jsp和validatesuccess.jsp两个视图,内容分别如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form:form modelAttribute="contentModel" method="post"> <form:errors path="*"></form:errors><br/><br/> name:<form:input path="name" /><br/> <form:errors path="name"></form:errors><br/> age:<form:input path="age" /><br/> <form:errors path="age"></form:errors><br/> email:<form:input path="email" /><br/> <form:errors path="email"></form:errors><br/> <input type="submit" value="Submit" /> </form:form> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!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>Insert title here</title> </head> <body> 验证成功! </body> </html>
其中特别要指出的是validatetest.jsp视图中<form:form modelAttribute="contentModel" method="post">的modelAttribute="xxx"后面的名称xxx必须与对应的@Valid @ModelAttribute("xxx") 中的xxx名称一致,否则模型数据和错误信息都绑定不到。
<form:errors path="name"></form:errors>即会显示模型对应属性的错误信息,当path="*"时则显示模型全部属性的错误信息。
运行测试:
直接点击提交:
可以看到正确显示了设置的错误信息。
填写错误数据提交:
可以看到依然正确显示了设置的错误信息。
填写正确数据提交:
可以看到验证成功。
下面是主要的验证注解及说明:
注解 | 适用的数据类型 | 说明 |
@AssertFalse | Boolean, boolean | 验证注解的元素值是false |
@AssertTrue | Boolean, boolean | 验证注解的元素值是true |
@DecimalMax(value=x) | BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence. | 验证注解的元素值小于等于@ DecimalMax指定的value值 |
@DecimalMin(value=x) | BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence. | 验证注解的元素值小于等于@ DecimalMin指定的value值 |
@Digits(integer=整数位数, fraction=小数位数) | BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence. | 验证注解的元素值的整数位数和小数位数上限 |
@Future | java.util.Date, java.util.Calendar; Additionally supported by HV, if theJoda Time date/time API is on the class path: any implementations ofReadablePartial andReadableInstant. | 验证注解的元素值(日期类型)比当前时间晚 |
@Max(value=x) | BigDecimal, BigInteger, byte, short,int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type of Number. | 验证注解的元素值小于等于@Max指定的value值 |
@Min(value=x) | BigDecimal, BigInteger, byte, short,int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of CharSequence (the numeric value represented by the char sequence is evaluated), any sub-type of Number. | 验证注解的元素值大于等于@Min指定的value值 |
@NotNull | Any type | 验证注解的元素值不是null |
@Null | Any type | 验证注解的元素值是null |
@Past | java.util.Date, java.util.Calendar; Additionally supported by HV, if theJoda Time date/time API is on the class path: any implementations ofReadablePartial andReadableInstant. | 验证注解的元素值(日期类型)比当前时间早 |
@Pattern(regex=正则表达式, flag=) | String. Additionally supported by HV: any sub-type of CharSequence. | 验证注解的元素值与指定的正则表达式匹配 |
@Size(min=最小值, max=最大值) | String, Collection, Map and arrays. Additionally supported by HV: any sub-type of CharSequence. | 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小 |
@Valid | Any non-primitive type(引用类型) | 验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象 |
@NotEmpty |
| 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@Range(min=最小值, max=最大值) |
| 验证注解的元素值在最小值和最大值之间 |
@NotBlank |
| 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
@Length(min=下限, max=上限) |
| 验证注解的元素值长度在min和max区间内 |
|
| 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
更多信息请参考官方文档:http://docs.jboss.org/hibernate/validator/4.3/reference/en-US/html/validator-usingvalidator.html