目录
一、JavaBean标准效验
Java官方对Bean的验证定义经历了三个版本的规范:
- Bean Validation 1.0(JSR-303)。主要是对JavaBean进行验证,比如Bean的属性是否可以为空。该规范定义了基于注解的JavaBean验证方式,常用的注解有@NotNull、@Max等。
- Bean Validation 1.1(JSR-349)。提供了方法级别的验证和依赖注入的验证支持。
- Bean Validation 2.0(JSR-380)。支持容器效验、日期效验(@Past、@Future)及拓展元素数据(@Email、@Positive和@Negative等)
Java官方只是提供了验证的标准接口(javax.validation),并没有提供具体的实现。使用Spring+Hibernate的组合进行开发就可以使用Hibernate的验证实现,但如果使用MyBatis等其他持久层框架,则选择Hibernate的Bean验证就不合适了。但Apache提供了Bval用来实现标准接口规范。使用JavaBean Validation的标准验证接口需要导入javax.validation依赖包及Bval实现的依赖包,使用Maven导入依赖配置如下:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- 除了使用org.apache.bval.bundle,还可以使用org.hibernate.validator。(具体可以视情形而定) -->
<dependency>
<groupId>org.apache.bval</groupId>
<artifactId>org.apache.bval.bundle</artifactId>
<version>2.0.0</version>
</dependency>
下面以User类中年龄的整型属性userAge为例,使用@Max注解限定最大用户年龄在100岁,属性的限定注解如下:
@Max(value = 100, message = "年龄太大了") //message用于指定验证不合格后的提示消息
private int userAge;
添加限定注解后,就可以构造验证器(Validation)对象的validate()方法对User类型对象中的属性值进行验证。
public static void main(String[] args) {
// 使用Validation构造器工厂并获取验证器(获取的就是Bval的验证器对象)
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
User user = new User();
user.setUserAge(180);
// 对user对象进行验证并返回验证结果集
Set<ConstraintViolation<User>> violations = validator.validate(user);
// 打印有问题的属性和错误信息
for (ConstraintViolation<User> data : violations) {
System.out.println(data.getPropertyPath().toString() + " --- " + data.getMessage());
}
// 控制台输出:userAge --- 年龄太大了
}
除@Max注解外,JavaBean Validation提供的常用限制注解及其分类如下图所示,这些限制注解可以使用在方法、字段、参数和构造器等元素中。
Validation会通过SPI的方式找到所有ValidationProvider接口的实现,只要加入依赖包,就会被自动加载。上面引入了Bval依赖包,也就是使用JavaBean 验证API的时候,默认使用Bval实现。
二、Spring核心容器的验证
数据绑定类DataBinder除了数据转换和绑定的功能之外,DataBinder最重要的作用就是可以用来验证绑定对象的有效型。Spring支持标准的JavaBean Validation及其相关实现(比如Bval)对JavaBean的验证,另外,Spring还支持方法级别的验证和依赖注入的验证。当然,也需要在Spring项目中导入验证标准接口及实现的依赖包。Spring会默认从classpath下找到可用的Bean Validation,除非需要自定义验证器,一般不需要显式地配置Validation。Spring可以在Bean初始化后对其进行有效性验证,也可以对方法级别地参数和返回值进行效验。
1.Bean有效性验证
Spring通过PostProcessor的初始化回调方式对Bean进行有效性效验,实现方式是配置BeanValidationPostProcessor的Bean,在XML中的配置如下:
<bean class="org.springframework.validation.beanvalidation.BeanValidationPostProcessor"></bean>
沿用上面对User类中的userAge进行验证,上面userAge属性已经有@Max(value = 100, message = "年龄太大了")的限定,如果Bean的配置如下:
<bean id = "user" class="com.mec.springmvc.model.User">
<property name="userAge" value="180"></property> <!-- 超过限制的设置 -->
</bean>
以上配置完成后,容器在初始化启动的时候就会提示初始化Bean失败的错误。
2.方法级别的有效性验证
传统的开发方式是开发者在方法代码中自行处理判断逻辑,比如参数是否有效,返回值是否为空。Spring提供了MethodValidationPostProcessor可以对方法参数和返回值进行验证,但同样需要配置初始化回调的Bean,在XML中配置如下:
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"></bean>
配置以上Bean之后,在需要验证的方法类上标注@Validated注解之后(用在类型、方法和方法参数上。但不能用于成员属性),就可以使用限制注解对参数和返回值进行限制设定了,示例如下:
@Validated // Spring验证注解。告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持
public class UserService {
// 添加了参数和返回值限制注解的方法。
// @Valid注解可以用在方法、构造函数、方法参数及返回值和成员属性上,注意如果被@Valid注解的类中有另外一个引用类B的成员b
// 那么要想成员b中的限制注解生效,则需要在该成员上添加@Valid。(也就是说验证对像为自定义引用类型的话需要逐层添加,也就是嵌套验证)
public @Valid User get(@NotNull(message = "参数不能为空") String name) {
User user = new User();
user.setUserAge(180);
return user;
}
}
由于userAge属性上添加了@Max注解对其进行了限制,且get()方法返回User对象,添加@Valid注解之后返回的user对象中的@Max限制注解就会生效。如果不合法会输出message里的值。
@Validated和@Valid注解的异同:
- @Validated和@Valid功能很类似,都可以在controller层开启数据校验功能。
- @Valid可以注解在成员属性(字段)上,但是@Validated不行(也是基于这一点,所以该注解不能做嵌套验证,但是可以配合@Valid做嵌套验证)。
- @Valid只能用在controller层的类上。@Validated可以用在其他被spring管理的类上。(比如这里的UserService,如果将其类上的注解换成@Valid则验证不能生效)
- @Validated和@Valid都可以用在controller层的参数前面,但这只能在controller层生效。
- @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。没有添加分组属性时,默认验证没有分组的验证属性;@Valid不支持分组。
BeanValidationPostProcessor和MethodValidationPostProcessor会自动查找类路径下的验证器并使用,也可以通过validator属性注入自定义的验证器。
三、Spring MVC容器的验证
在SpringMVC中,前端参数传递通过WebDataBinder转换为后端类型对象,同时可以对转换的对象进行效验。在SpringMVC的请求映射方法中,前端请求参数会自动匹配请求方法的对象参数,在请求方法的参数中可以使用@Validated注解对装配的参数进行验证。验证出错不会像对Bean和方法的验证那样抛出异常,而是记录到BindingResult对象中,在该请求处理方法中增加一个BindingResult的参数就可以获取BindingResult的结果了,如下:
@RequestMapping("/saveUser")
public User save(@Validated User user, BindingResult bindingResult) { //BindingResult必须跟在实体类之后
List<ObjectError> list = bindingResult.getAllErrors();
for (ObjectError objectError : list) {
FieldError fe = (FieldError) objectError;
System.out.println(fe.getField());// 错误的属性:userAge
System.out.println(fe.getRejectedValue());// 错误的值180
System.out.println(fe.getCode());// 错误码,Max
}
return user;
}
BindingResult除了可以获取错误信息外还可以得到目标对象,模型对象和属性编辑器注册器(PropertyEditorRegistry)。
四、验证器配置及增加自定义验证器
由于Validation会通过SPI的方式找到所有ValidationProvider接口的实现,所以一般情况下不需要对验证器进行显式的配置,如果有多个验证器实现需要选择或者需自定义错误信息,在SpringMVC中通过指定可以通过<mvc:annotation-driven>的validator属性指定验证器。JavaBean Validation标准接口有多个实现,常见的有BVal和Hibernate。配置示例如下:
<mvc:annotation-driven validator="validator" />
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- 校验器提供类,这里使用Bval的校验。当然还可以指定org.hibernate.validator.HibernateValidator,前提是有相关的依赖包 -->
<property name="providerClass" value="org.apache.bval.jsr.ApacheValidationProvider"></property>
</bean>
自定义验证器
//org.springframework.validation.Validator
public class UserValidator implements Validator {
// 用于限定验证对象的类型
@Override
public boolean supports(Class<?> clazz) {
return User.class.isAssignableFrom(clazz); // 用于判断类是否为给定类或者其子类
}
// 验证的方法,其中,errors用于存放验证失败的信息
@Override
public void validate(Object target, Errors errors) {
User user = (User) target;
int userAge = user.getUserAge();
if (userAge < 0) {
errors.rejectValue("age", "非法年龄值"); //添加错误信息
} else if (userAge > 120) {
errors.rejectValue("age", "年龄过大");
}
}
}
自定义验证器可以结合@InitBinder添加到某个控制器的WebDataBinder中。添加方式是使用WebDataBinder的addValidators()和setValidator()方法来添加和设置验证器,该验证器可使用在整个控制器中。
@Controller
public class UserWithValidatorController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserValidator()); // 添加自定义验证器
}
@RequestMapping(value = "/addUserWithValidator")
// 通过initBinder方法添加了自定义验证器,使用Validated注解会自动调用UserValidator。
public ModelAndView add(@Validated User user, BindingResult bindingResult) {
ModelAndView mv = new ModelAndView();
if (bindingResult.hasErrors()) {
List<ObjectError> validateErrorList = bindingResult.getAllErrors();
mv.addObject("validateErrorList", validateErrorList);
}
mv.addObject("user", user);
mv.setViewName("login");
return mv;
}
}