数据验证处理
本篇文章主要讲解Spring 提供的对象合法性验证接口org.springframework.validation.Validator
,顺带提及一下javax.validation.Validator
,注意本篇文章未加特别说明的Validator
一律指org.springframework.validation.Validator
#
Validator接口说明org.springframework.validation.Validator
是Spring 为我们提供了一套验证对象的接口,接口定义如下:
public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors);}
从定义上我们可以猜到Validator
主要就是验证对象是否合法,并将验证结果添加到Errors
对象中。多的不解释,先上几行代码!
// (1) javax.validation.Validator private final Validator nativeValidator = Validation.buildDefaultValidatorFactory().getValidator(); // (2) SpringValidatorAdapter 对象实现了javax.validation.Validator 和 org.springframework.validation.Validator两接口 private final SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(nativeValidator); //messageSource private final StaticMessageSource messageSource = new StaticMessageSource();
@Test // SPR-13406 public void testNoStringArgumentValue() throws Exception { // (3) 需要被验证的对象 TestBean testBean = new TestBean(); testBean.setPassword("pass"); testBean.setConfirmPassword("pass"); // (4) Error对象,用于存储验证错误信息 BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean"); //实际验证方法 validatorAdapter.validate(testBean, errors); //获取验证结果 assertThat(errors.getFieldErrorCount("password"), is(1)); assertThat(errors.getFieldValue("password"), is("pass")); FieldError error = errors.getFieldError("password"); assertNotNull(error); // (5) 利用messageSource获取错误描述信息 assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Size of Password must be between 8 and 128")); //检查错误原因 assertTrue(error.contains(ConstraintViolation.class)); //获取具体错误原因对象 assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password")); assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString())); }
以上是org.springframework.validation.beanvalidation2.SpringValidatorAdapterTests
单元测试片段代码,我们主要从上面标注的([1,2,3,4,5])
来入手了解我们的Spring Validator的功能与原理
#
(1) javax.validation.Validator这是validation-api
中的比较重量级的接口,其功能也是验证对象的合法性,Constrain once, validate everywhere
这是官网标语。我们在来看一张图
这张图明确表明了,validation-api
版本与JSR对应关系,如果不知道JSR是什么,老铁这里不用纠结,你只需要知道,这里的JSR就是验证对象合法性的一些规范(而实现了这套规范的有hibernate-validator
)如下图是一些注解规范:
这些规范语义很清楚,就不过多解释。那么javax.validation.Validator
为什么会出现在这里了?很简单,一句话不要重复造轮子
在这里体现,也就是说Spring 的Validator
是包含了我们JSR303
,JSR349
等规范了的,并且还是使用的hibernate-validator
实现。
#
(2) SpringValidatorAdapterSpringValidatorAdapter是Validator
的一个实现类,我们在看看其类图,可以知道SpringValidatorAdapter
是实现了Validator
和javax.validation.Validator
两个接口的。
#
(3) 需要被验证的对象我们看看TestBean
的定义
@Same(field = "password", comparingField = "confirmPassword")@Same(field = "email", comparingField = "confirmEmail")static class TestBean {
@Size(min = 8, max = 128) private String password;
private String confirmPassword;
@Pattern(regexp = "[\\w.'-]{1,}@[\\w.'-]{1,}") private String email;
@Pattern(regexp = "[\\p{L} -]*", message = "Email required") private String confirmEmail;}
可以看到这个类已经被很多注解修饰,这里的注解也就是我们需要验证的规则。@Size
和@Pattern
是validator-api
中定义的hibernate-validator
已经完全实现了验证功能,而@Same
是Spring Test自定义的
注解,如下:
@Documented@Constraint(validatedBy = {SameValidator.class})@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Repeatable(SameGroup.class)@interface Same { 。。。。}
这里我们重点看@Constraint(validatedBy = {SameValidator.class})
这就是@Same
验证逻辑的实现类,当然这个类必须实现ConstraintValidator
接口,以下是接口定义
public interface ConstraintValidator<A extends Annotation, T> { //注解的元信息 default void initialize(A constraintAnnotation) { } //验证方法 boolean isValid(T value, ConstraintValidatorContext context);}
#
(4) Errors对象,用于存储验证错误信息通过下图左边可以看到Errors接口提供了添加错误信息
和获取错误信息
的接口,这也是Spring为我们提供的统一处理和获取Error信息的接口。而右边就是我们BeanPropertyBindingResult
的一个关系类图,也就Errors
接口的具体实现类。
#
(5) 利用messageSource获取错误描述信息这里很和谐的和Spring的MessageResource
搭配使用。以下是DefaultMessageCodesResolver.resolveMessageCodes
方法为生成code的规则。
#
总结现在我们在对Spring Validator
的功能做次总结
- 支持 hibernate-validate 实现的 validator-api
- 对错误信息提供了友好的封装
- 错误信息能够无缝结合MessageSource,以便提供国际化支持
#
使用#
Service 中使用@Service@Validated //Validated 注意 一定要在类或接口上 因为切面是用的 //Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true) // 代码源自MethodValidationPostProcessorpublic class HelloService {
//参数使用 Valid public void say(@Valid Hello hello) { System.out.println("xxx"); }}
#
Controller 中使用@RequestMapping("say")//此处使用Validated 或者 Valid 都可以//ModelAttributeMethodProcessor.determineValidationHints 判断 Validated 或者 Valid //使用Errors 可以接收错误信息,不用Errors会直接抛异常到前端public String say(@Validated Hello hehe, Errors errors)
无论是在service,还是controller中的使用,spring 最终都是用 hibernate
的 ValidatorImpl
做的校验