Java Bean Validation
https://beanvalidation.org/2.0/spec/#constraintsdefinitionimplementation-validationimplementation
Java Bean Validation 是什么?
Java Bean Validation 是一个规范,为了给开发人员提供一个对象级的约束声明和验证工具,以及约束元数据存储库和查询api。最早定义在JSR303,经过版本的迭代,从JSR303到JSR349,再到最新的JSR380,也就是现在说的 Bean Validation 2.0。
验证数据是贯穿应用程序的,包括任意一层。通常如果在每层单独进行校验不仅耗时,还会是代码变得冗余。为了避免这种情况,Bean Validation 允许开发人员将验证逻辑直接捆绑到域模型中,将验证逻辑和域模型的代码写在一起。
通常是通过注解的方式进行约束,也可以支持xml
如何定义约束?
约束:被校验的参数应该满足的条件
约束的定义是由约束注解和约束校验的实现来组合使用完成的。
约束注解
约束注解可以作用在 types(类,接口), fields(属性), methods(方法), constructors(构造器), parameters(参数), container elements(容器元素)以及在组合使用的场景还可以用在其他约束注解上
约束注解还必须被 javax.validation.Constraint 标注
先介绍一下 Constraint 注解
@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Constraint {
Class extends ConstraintValidator, ?>>[] validatedBy();
}
真正的校验逻辑在 validatedBy() 中指定的类中进行,该类必须继承 ConstraintValidator 类。并且必须实现 initialize 方法和 isValid 方法。关于ConstraintValidator后面在实现自定义注解的时候会介绍ConstraintValidator
除了被Constraint注解标注外,约束注解还具有以下属性。
String message() default "{com.acme.constraint.MyConstraint.message}";
每一个约束注解必须定义一个message元素,用来设置校验失败时的错误信息Class>[] groups() default {};
groups 元素被定义成有一个class数组组成,默认值是空数组。groups可以用来控制约束的顺序和对javaBean进行部分状态校验。比如比如被标注的groups包含方法上指定的groups时,才进行校验Class extends Payload>[] payload() default {};
payLoad() 元素是由实现了Payload的类的数组组成。payLoad 可以将元数据信息和约束生命关联起来。 payLoad的介绍参考:https://www.logicbig.com/tutorials/java-ee-tutorial/bean-validation/constraint-payload.html`ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
validationAppliesTo用来声明约束的目标(ConstraintTarget.IMPLICIT;ConstraintTarget.RETURN_VALUE;ConstraintTarget.PARAMETERS)
例:
//assuming OrderNumberValidator is a generic constraint validator
package com.acme.constraint;
/**
* Mark a String as representing a well formed order number
*/
@Documented
@Constraint(validatedBy = OrderNumberValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface OrderNumber {
String message() default "{com.acme.constraint.OrderNumber.message}";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
多个相同的约束注解可以同时使用。
public class Address {
@ZipCode(countryCode = "fr", groups = Default.class, message = "zip code is not valid")
@ZipCode(
countryCode = "fr",
groups = SuperUser.class,
message = "zip code invalid. Requires overriding before saving."
)
private String zipCode;
}
同时也可以组合使用
@Pattern(regexp = "[0-9]*")
@Size(min = 5, max = 5)
@Constraint(validatedBy = FrenchZipCodeValidator.class)
@Documented
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface FrenchZipCode {
String message() default "Wrong zip code";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
FrenchZipCode[] value();
}
}
约束校验实现
约束校验实现类必须是ConstraintValidator接口的实现
public interface ConstraintValidator {
default void initialize(A constraintAnnotation) {
}
boolean isValid(T value, ConstraintValidatorContext context);
}
范型A表示这个是实现类被哪个约束注解使用,也就是Constraint注解的validatedBy设置的值(Constraint)
真正校验的逻辑是在isvalid方法中实现的。参考下面的例子
public class CollectionSizeLimitValidator implements ConstraintValidator> {
private int limitSize;
@Override
public void initialize(CollectionSizeLimit constraintAnnotation) {
limitSize = constraintAnnotation.limitSize();
}
@Override
public boolean isValid(Collection> objects, ConstraintValidatorContext constraintValidatorContext) {
if(CollectionUtils.isEmpty(objects) || limitSize
自此约束就被定义好了,被定义好的约束注解标注到对应的元素上就可以对参数进行约束
hibenate-validator – Java Bean Validation的实现
前面提到Java Bean Validation只是一个规范,而hibenate-validator则是对规范的具体实现
上面提到了如果定义一个约束。接下来介绍如何使用hibenate-validator进行校验
- 获取Validator实例
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
- Validator接口包含3个方法可以用来对整个实体或者单个属性进行校验
- Validator#validate() 对给定的标注了约束注解的属性的bean进行校验
Car car = new Car( null, true );
Set> constraintViolations = validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
- Validator#validateProperty() 对给定对象的单个属性进行校验
Car car = new Car( null, true );
Set> constraintViolations = validator.validateProperty(
car,
"manufacturer"
);
assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
- Validator#validateValue() 通过使用validateValue()方法,您可以检查给定类的单个属性是否可以被成功验证,如果该属性具有指定的值
Set> constraintViolations = validator.validateValue(
Car.class,
"manufacturer",
null
);
assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
Java Bean Validation 声明了一些约束,同样hibernate-validator也创建了一些额外的约束。参照https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-builtin-constraints 了解所有支持的约束
使用spring 应该如何进行参数校验
spring validator
将验证视为业务逻辑有利有弊,spring设计了一个校验的框架。validation包下主要有dataBinder和validator两部分。
Validator是一个接口,类通过实现Validator接口,并实现 supports 方法和 validate 方法来完成一个校验器的编写。错误信息会放到Errors中,
public class PersonValidator implements Validator {
/**
* This Validator validates only Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
然后借助DataBuinder的validate方法完成校验
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
这种方式和Java Bean Validation比 使用起来明显很繁琐
所以spring validation实现了对Java Bean Validation的适配,完成了救赎
LocalValidatorFactoryBean 类既实现了javaBeanValidation 的 javax.validation.ValidatorFactory , javax.validation.Validator 接口,同样也实现了spring 的org.springframework.validation.Validator。所以可以看出LocalValidatorFactoryBean其实是一个适配或者说整合spring Validation和java Bean validation的校验功能的类
如果classPath中存在Java Bean Validation,LocalValidatorFactoryBean 会被注册成全局的validator。
public Validator mvcValidator() {
Validator validator = getValidator();
if (validator == null) {
if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
Class> clazz;
try {
//这里的OptionalValidatorFactoryBean是LocalValidatorFactoryBean的子类
String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
}
catch (ClassNotFoundException | LinkageError ex) {
throw new BeanInitializationException("Failed to resolve default validator class", ex);
}
validator = (Validator) BeanUtils.instantiateClass(clazz);
}
else {
validator = new NoOpValidator();
}
}
return validator;
}
LocalValidatorFactoryBean的父类SpringValidatorAdapter中定义了
private javax.validation.Validator targetValidator;
真正的validate操作会委派给这个对象,最终进行的还是Java Bean Validation的校验。
public void validate(Object target, Errors errors) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target), errors);
}
}
所以 spring 虽然自己定义了一套参数校验的规则,但是由于使用起来并不便利。最终还是对Java Bean Validation进行了适配。