1. 程式人生 > 其它 >spring boot hibernate validate 原理

spring boot hibernate validate 原理

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 異常

擴充套件

在自定義註解攔截的時候,我們可以這樣實現:

  1. 通過@Aspect 定義切面方式,使用 @Before, @Around 註解實現
  2. 也可以通過實現spring 中 BeanPostProcessor ,在後置處理器中建立增強, 如繼承 AbstractBeanFactoryAwareAdvisingPostProcessor