Java Bean Validation
https://beanvalidation.org/2.0/spec/#constraintsdefinitionimplementation-validationimplementation
Java Bean Validation 是什么?
Java Bean Validation 是一個規(guī)范算行,為了給開發(fā)人員提供一個對象級的約束聲明和驗證工具夷恍,以及約束元數據存儲庫和查詢api王暗。最早定義在JSR303,經過版本的迭代涩澡,從JSR303到JSR349,再到最新的JSR380,也就是現在說的 Bean Validation 2.0昌执。
驗證數據是貫穿應用程序的,包括任意一層诈泼。通常如果在每層單獨進行校驗不僅耗時懂拾,還會是代碼變得冗余。為了避免這種情況铐达,Bean Validation 允許開發(fā)人員將驗證邏輯直接捆綁到域模型中岖赋,將驗證邏輯和域模型的代碼寫在一起。
通常是通過注解的方式進行約束瓮孙,也可以支持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進行部分狀態(tài)校驗夸溶。比如比如被標注的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<A extends Annotation, T> {
default void initialize(A constraintAnnotation) {
}
boolean isValid(T value, ConstraintValidatorContext context);
}
范型A表示這個是實現類被哪個約束注解使用,也就是Constraint注解的validatedBy設置的值(Constraint)
真正校驗的邏輯是在isvalid方法中實現的望薄。參考下面的例子
public class CollectionSizeLimitValidator implements ConstraintValidator<CollectionSizeLimit, Collection<?>> {
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<objects.size()){
return false;
}
return true;
}
}
自此約束就被定義好了,被定義好的約束注解標注到對應的元素上就可以對參數進行約束
hibenate-validator – Java Bean Validation的實現
前面提到Java Bean Validation只是一個規(guī)范呼畸,而hibenate-validator則是對規(guī)范的具體實現
上面提到了如果定義一個約束痕支。接下來介紹如何使用hibenate-validator進行校驗
- 獲取Validator實例
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
- Validator接口包含3個方法可以用來對整個實體或者單個屬性進行校驗
- Validator#validate() 對給定的標注了約束注解的屬性的bean進行校驗
Car car = new Car( null, true );
Set<ConstraintViolation<Car>> 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<ConstraintViolation<Car>> constraintViolations = validator.validateProperty(
car,
"manufacturer"
);
assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
- Validator#validateValue() 通過使用validateValue()方法,您可以檢查給定類的單個屬性是否可以被成功驗證蛮原,如果該屬性具有指定的值
Set<ConstraintViolation<Car>> 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也創(chuàng)建了一些額外的約束。參照https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-builtin-constraints 了解所有支持的約束
使用spring 應該如何進行參數校驗
spring validator
將驗證視為業(yè)務邏輯有利有弊儒陨,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 雖然自己定義了一套參數校驗的規(guī)則窄赋,但是由于使用起來并不便利。最終還是對Java Bean Validation進行了適配楼熄。