Spring提供的@Validated和MethodValidationPostProcessor完成数据校验
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring提供的@Validated和MethodValidationPostProcessor完成数据校验相关的知识,希望对你有一定的参考价值。
Spring提供的@Validated和MethodValidationPostProcessor完成数据校验
@Validated怎么用
@Validated(Default.class)
public interface HelloService
Object hello(@NotNull @Min(10) Integer id, @NotNull String name);
// 实现类如下
@Slf4j
@Service
public class HelloServiceImpl implements HelloService
@Override
public Object hello(Integer id, String name)
return null;
向容器里注册一个处理器:
@Configuration
public class RootConfig
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor()
return new MethodValidationPostProcessor();
测试:
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AipsiCwsSupplierServiceApplication.class)
public class TestSpringBean
@Autowired
private HelloService helloService;
@Test
public void test1()
System.out.println(helloService.getClass());
helloService.hello(1, null);
结果如图:
完美的校验住了方法入参。
注意此处的一个小细节:若你自己运行这个案例你得到的参数名称可能是hello.args0等,而我此处是形参名。是因为我使用Java8的编译参数:-parameters(此处说一点:若你的逻辑中强依赖于此参数,务必在你的maven中加入编译插件并且配置好此编译参数)
若需要校验方法返回值,改写如下:
@NotNull
Object hello(Integer id);
// 此种写法效果同上
//@NotNull Object hello(Integer id);
运行:
javax.validation.ConstraintViolationException: hello.<return value>: 不能为null
...
校验完成。就这样借助Spring+JSR相关约束注解,就非常简单明了,语义清晰的优雅的完成了方法级别(入参校验、返回值校验)的校验。
校验不通过的错误信息,再来个全局统一的异常处理,就能让整个工程都能尽显完美之势。(错误消息可以从异常ConstraintViolationException的getConstraintViolations()方法里获得的)
原理时间到
MethodValidationPostProcessor
它是Spring提供的来实现基于方法Method的JSR校验的核心处理器~它能让约束作用在方法入参、返回值上,如:
public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
官方说明:方法里写有JSR校验注解要想其生效的话,要求类型级别上必须使用@Validated标注(还能指定验证的Group)
这里的方法指的是非controller层的方法,如果是controller的方法,会在数据绑定,即DataBinder处理过程中完成数据校验,这也是为什么我们平时在controller方法上使用约束注解的时候,不需要加@Validated的原因
源码解析
要讲清楚MethodValidationPostProcessor,就必须要从ProxyProcessorSupport说起,我们可以看一下ProxyProcessorSupport的继承体系。
关于AOP部分,在我的Spring源码剖析专栏,已经进行了详细的分析,这里就不多展开了,但是之前一直讲的都是被green圈起来的那部分自动代理创建器,因此本节源码分析重点是在被red圈起来的这部分。
先从AbstractAdvisingBeanPostProcessor讲起。
AbstractAdvisingBeanPostProcessor—尝试利用内部advisor对bean进行dialing
AbstractAdvisingBeanPostProcessor继承了ProxyProcessorSupport 并且实现了BeanPostProcessor 接口,该类主要就是利用其内部的Advisor 来尝试对bean进行代理。
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor
//内部的增强器
@Nullable
protected Advisor advisor;
//如果当前bean是被代理过的,并且当前bean满足内部Advisor的增强条件,那么需要将内部Advisor加入到被代理bean的
//Advisor集合中哪一个位置中去
//如果下面这个标记为真,那就放到第一个位置
protected boolean beforeExistingAdvisors = false;
//缓存作用: 对于多例bean而言,每一次获取都需要进行增强判断,太麻烦,这里直接缓存,就可以提高效率
private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);
public void setBeforeExistingAdvisors(boolean beforeExistingAdvisors)
this.beforeExistingAdvisors = beforeExistingAdvisors;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
return bean;
//在相关初始化方法被调用后,调用该回调接口
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
//如果内部没有设置增强器或者bean是spring内部的bean,那么就跳过
if (this.advisor == null || bean instanceof AopInfrastructureBean)
// Ignore AOP infrastructure such as scoped proxies.
return bean;
//如果当前bean已经被代理过了
if (bean instanceof Advised)
Advised advised = (Advised) bean;
//AopUtils.getTargetClass(bean)拿到被代理的目标对象类型,然后利用isEligible方法,判断是否需要被当前内部的advisor增强
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean)))
// Add our local Advisor to the existing proxy's Advisor chain...
//如果需要被内部的advisor增强的话,再判断内部的这个advisor应该加入当前代理对象对应advisor集合中哪一个位置
if (this.beforeExistingAdvisors)
advised.addAdvisor(0, this.advisor);
else
advised.addAdvisor(this.advisor);
return bean;
//如果当前bean没有被代理,那就还是判断当前bean是否需要被增强
if (isEligible(bean, beanName))
//如果需要,那么准备为当前bean创建代理对象
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
//isProxyTargetClass为真,说明需要采用cglib代理
if (!proxyFactory.isProxyTargetClass())
//采用jdk代理,将代理对象需要实现的接口,加入proxyFactory对应的接口集合中
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
//将当前增强器加入
proxyFactory.addAdvisor(this.advisor);
//钩子接口---子类覆写可以增添额外的逻辑
customizeProxyFactory(proxyFactory);
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader())
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
//创建代理
return proxyFactory.getProxy(classLoader);
// No proxy needed.
return bean;
//判断当前bean是否需要被增强
protected boolean isEligible(Object bean, String beanName)
return isEligible(bean.getClass());
protected boolean isEligible(Class<?> targetClass)
//缓存中有的话,说明是需要被增加的
Boolean eligible = this.eligibleBeans.get(targetClass);
if (eligible != null)
return eligible;
//没有增强的话,那就不需要
if (this.advisor == null)
return false;
//利用advisor内部的pointcut进行判断
eligible = AopUtils.canApply(this.advisor, targetClass);
this.eligibleBeans.put(targetClass, eligible);
return eligible;
protected ProxyFactory prepareProxyFactory(Object bean, String beanName)
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
proxyFactory.setTarget(bean);
return proxyFactory;
protected void customizeProxyFactory(ProxyFactory proxyFactory)
这部分与Spring AOP联系密切,如果没搞懂的话,可以先去看一下我的AOP源码解析,在我的Spring 源码解析专栏中
AbstractBeanFactoryAwareAdvisingPostProcessor----结合IOC
AbstractBeanFactoryAwareAdvisingPostProcessor该类基本没有做什么额外多的逻辑,因此大家也没必要太在意。
public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends AbstractAdvisingBeanPostProcessor
implements BeanFactoryAware
@Nullable
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory)
this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory ?
(ConfigurableListableBeanFactory) beanFactory : null);
@Override
protected ProxyFactory prepareProxyFactory(Object bean, String beanName)
if (this.beanFactory != null)
AutoProxyUtils.exposeTargetClass(this.beanFactory, beanName, bean.getClass());
ProxyFactory proxyFactory = super.prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass() && this.beanFactory != null &&
//如果bean对应的beanDefinition中的PRESERVE_TARGET_CLASS_ATTRIBUTE属性被设置了,说明也需要采用cglib代理
AutoProxyUtils.shouldProxyTargetClass(this.beanFactory, beanName))
proxyFactory.setProxyTargetClass(true);
return proxyFactory;
@Override
protected boolean isEligible(Object bean, String beanName)
return (!AutoProxyUtils.isOriginalInstance(beanName, bean.getClass()) &&
super.isEligible(bean, beanName));
MethodValidationPostProcessor
// @since 3.1
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean
// 备注:此处你标注@Valid是无用的~~~Spring可不提供识别
// 当然你也可以自定义注解(下面提供了set方法~~~)
// 但是注意:若自定义注解的话,此注解只决定了是否要代理,并不能指定分组哦 so,没啥事别给自己找麻烦吧
private Class<? extends Annotation> validatedAnnotationType = Validated.class;
// 这个是javax.validation.Validator
@Nullable
private Validator validator;
// 可以自定义生效的注解
public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType)
Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
this.validatedAnnotationType = validatedAnnotationType;
// 这个方法注意了:你可以自己传入一个Validator,并且可以是定制化的LocalValidatorFactoryBean哦~(推荐)
public void setValidator(Validator validator)
// 建议传入LocalValidatorFactoryBean功能强大,从它里面生成一个验证器出来靠谱
if (validator instanceof LocalValidatorFactoryBean)
this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
else if (validator instanceof SpringValidatorAdapter)
this.validator = validator.unwrap(Validator.class);
else
this.validator = validator;
// 当然,你也可以简单粗暴的直接提供一个ValidatorFactory即可~
public void setValidatorFactory(ValidatorFactory validatorFactory)
this.validator = validatorFactory.getValidator();
// 毫无疑问,Pointcut使用AnnotationMatchingPointcut,并且支持内部类哦~
// 说明@Aysnc使用的也是AnnotationMatchingPointcut,只不过因为它支持标注在类上和方法上,所以最终是组合的ComposablePointcut
// 至于Advice通知,此处一样的是个`MethodValidationInterceptor`~~~~
@Override
public void afterPropertiesSet()
//AnnotationMatchingPointcut默认只会进行类匹配,去判断当前类上是否标注了对应的注解
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
// 这个advice就是给@Validation的类进行增强的~ 说明:子类可以覆盖哦~
// @since 4.2
protected Advice createMethodValidationAdvice(@Nullable Validator validator)
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
它是个普通的BeanPostProcessor,为Bean创建的代理的时机是postProcessAfterInitialization(),也就是在Bean完成初始化后有必要的话用一个代理对象返回进而交给Spring容器管理~(同@Aysnc)
容易想到,关于校验方面的逻辑不在于它,而在于切面的通知:MethodValidationInterceptor
MethodValidationInterceptor
它是AOP联盟类型的通知,此处专门用于处理方法级别的数据校验。
注意理解方法级别:方法级别的入参有可能是各种平铺的参数、也可能是一个或者多个对象
// @since 3.1 因为它校验Method 所以它使用的是javax.validation.executable.ExecutableValidator
public class MethodValidationInterceptor implements MethodInterceptor
// javax.validation.Validator
private final Validator validator;
// 如果没有指定校验器,那使用的就是默认的校验器
public MethodValidationInterceptor()
this(Validation.buildDefaultValidatorFactory());
public MethodValidationInterceptor(ValidatorFactory validatorFactory)
this(validatorFactory.getValidator());
public MethodValidationInterceptor(Validator validator)
this.validator = validator;
@Override
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable
// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
// 如果是FactoryBean.getObject() 方法 就不要去校验了~
if (isFactoryBeanMetadataMethod(invocation.getMethod()))
return invocation.proceed();
Class<?>[] groups = determineValidationGroups(invocation);
// Standard Bean Validation 1.1 API ExecutableValidator是1.1提供的
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result; // 错误消息result 若存在最终都会ConstraintViolationException异常形式抛出
try
// 先校验方法入参
result = execVal.validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
catch (IllegalArgumentException ex)
// 此处回退了异步:找到bridged method方法再来一次
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 = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
if (!result.isEmpty())
throw new ConstraintViolationException(result);
return returnValue;
// 找到这个方法上面是否有标注@Validated注解 从里面拿到分组信息
// 备注:虽然代理只能标注在类上,但是分组可以标注在类上和方法上哦~~~~
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])Spring中@Valid和@Validated的区别
用spring的@Validated注解和org.hibernate.validator.constraints.*的一些注解在后台完成数据校验