spring boot hibernate validate 原理
阿新 • • 發佈:2022-05-16
spring boot hibernate validate 原理
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
引入後自動 引入hibernate-validator
使用
@Data
public class User {
@NotNull
@Length(min = 2, max = 10)
private String userName;
}
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
return Result.ok();
}
- 使用約束 @NotNull …
- 加上 @Validated 或者 @Valid
原理
為什麼加上 @Validated 或者 @Valid
註解後就可以實現引數校驗, 關鍵就是 MethodValidationPostProcessor
MethodValidationPostProcessor
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean {
private Class<? extends Annotation> validatedAnnotationType = Validated.class;
@Nullable
private Validator validator;
public MethodValidationPostProcessor() {
}
public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) {
Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
this.validatedAnnotationType = validatedAnnotationType;
}
public void setValidator(Validator validator) {
if (validator instanceof LocalValidatorFactoryBean) {
this.validator = ((LocalValidatorFactoryBean)validator).getValidator();
} else if (validator instanceof SpringValidatorAdapter) {
this.validator = (Validator)validator.unwrap(Validator.class);
} else {
this.validator = validator;
}
}
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.validator = validatorFactory.getValidator();
}
public void afterPropertiesSet() {
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
this.advisor = new DefaultPointcutAdvisor(pointcut, this.createMethodValidationAdvice(this.validator));
}
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
return validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor();
}
}
- afterPropertiesSet
- 建立切點
- 為切面 新增 增強 advice
這個增強就是 使用類代理實現 MethodValidationInterceptor
MethodValidationInterceptor
public class MethodValidationInterceptor implements MethodInterceptor {
private final Validator validator;
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
Class<?>[] groups = determineValidationGroups(invocation);
// Standard Bean Validation 1.1 API
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
try {
result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass()));
result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
Object returnValue = invocation.proceed();
result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
private boolean isFactoryBeanMetadataMethod(Method method) {
Class<?> clazz = method.getDeclaringClass();
// Call from interface-based proxy handle, allowing for an efficient check?
if (clazz.isInterface()) {
return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
!method.getName().equals("getObject"));
}
// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
Class<?> factoryBeanType = null;
if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = SmartFactoryBean.class;
}
else if (FactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = FactoryBean.class;
}
return (factoryBeanType != null && !method.getName().equals("getObject") &&
ClassUtils.hasMethod(factoryBeanType, method));
}
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
if (validatedAnn == null) {
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
}
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}
}
- 方法上是否加了 @Validated 註解
- 使用 Validator 校驗
- 如果校驗失敗,則丟擲 ConstraintViolationException 異常
擴充套件
在自定義註解攔截的時候,我們可以這樣實現:
- 通過@Aspect 定義切面方式,使用 @Before, @Around 註解實現
- 也可以通過實現spring 中 BeanPostProcessor ,在後置處理器中建立增強, 如繼承
AbstractBeanFactoryAwareAdvisingPostProcessor