概述
MethodValidationInterceptor
是Spring Context
提供的一个MethodInterceptor
实现,它使用一个指定的JSR-303
验证器对使用了相应JSR-303
验证注解的方法参数或者返回值做验证。例子 :
// 注意,同时所属类上要使用 Spring 注解 @Validated
public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
该例子约定如下验证逻辑 :
- 参数
arg1
必须不能为null
; - 参数
arg2
最大值为10; - 方法
myValidMethod
返回值不能是null
;
另外,类级别使用的Spring
注解@Validated
可以定义验证组,该验证注解会应用到该类所有public
方法上。缺省情况下,JSR-303
会仅仅使用缺省验证组进行验证。
从Spring 5.0
开始,MethodValidationInterceptor
需要的验证器必须是Bean Validation 1.1
版本。
关于MethodValidationInterceptor
的应用,可以参考:Spring BeanPostProcessor : MethodValidationPostProcessor,这篇文章中,MethodValidationPostProcessor
会引入MethodValidationInterceptor
。而进一步关于MethodValidationPostProcessor
和所使用到的Validator
,可以参考:Spring Boot 自动配置 : ValidationAutoConfiguration,对于一个Spring Boot
应用,它会通过ValidationAutoConfiguration
定义两个bean
:一个是Validator
,一个是MethodValidationPostProcessor
。
源代码
源代码版本
Spring Context 5.1.5.RELEASE
package org.springframework.validation.beanvalidation;
// 省略 import 行
public class MethodValidationInterceptor implements MethodInterceptor {
// 要使用的 JSR-303 验证器,需要通过构造函数被外部指定
private final Validator validator;
/**
* Create a new MethodValidationInterceptor using a default JSR-303 validator underneath.
*/
public MethodValidationInterceptor() {
this(Validation.buildDefaultValidatorFactory());
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory.
* @param validatorFactory the JSR-303 ValidatorFactory to use
*/
public MethodValidationInterceptor(ValidatorFactory validatorFactory) {
this(validatorFactory.getValidator());
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 Validator.
* @param validator the JSR-303 Validator to use
*/
public MethodValidationInterceptor(Validator validator) {
this.validator = validator;
}
// MethodInterceptor 接口约定的方法 , 逻辑实现主流程如下 :
// 1. 执行方法参数验证逻辑
// 2. 调用目标方法
// 3. 执行方法返回值验证逻辑
@Override
@SuppressWarnings("unchecked")
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;
// 方法参数上的验证逻辑,验证结果保存在 result 中,如果验证失败, result 不为空,
// 此时会抛出异常 ConstraintViolationException
try {
result = execVal.validateParameters(
invocation.getThis(), 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(), invocation.getThis().getClass()));
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
// 调用目标方法
Object returnValue = invocation.proceed();
// 对方法执行的返回值进行验证 ,验证结果保存在 result 中,如果验证失败, result 不为空,
// 此时会抛出异常 ConstraintViolationException
result = execVal.validateReturnValue(invocation.getThis(), 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.getName(), method.getParameterTypes()));
}
/**
* Determine the validation groups to validate against for the given method invocation.
* <p>Default are the validation groups as specified in the {@link Validated} annotation
* on the containing target class of the method.
* @param invocation the current MethodInvocation
* @return the applicable validation groups as a Class array
*/
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
if (validatedAnn == null) {
validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class);
}
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}
}