用法
public interface UserService {
public UserModel get2( Integer uuid) ;
}
@Validated //① 告訴MethodValidationPostProcessor此Bean需要開啟方法級(jí)別驗(yàn)證支持
@Component
public class UserServiceImpl implement UserService {
public @NotNull UserModel get2(@NotNull @Min(value = 1) Integer uuid) { //②聲明前置條件/后置條件
//獲取 User Model
UserModel user = new UserModel(); //此處應(yīng)該從數(shù)據(jù)庫(kù)獲取
if(uuid > 100) {//方便后置添加的判斷(此處假設(shè)傳入的uuid>100 則返回null)
return null;
}
return user;
}
}
<!--注冊(cè)方法驗(yàn)證的后處理器-->
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
@validated和@valid不同點(diǎn)
在spring項(xiàng)目中唉工,@validated和@valid功能很類似,都可以在controller層開啟數(shù)據(jù)校驗(yàn)功能乏盐。
但是@validated和@valid又不盡相同佳窑。有以下不同點(diǎn):
- 分組
- 注解地方,@Valid可以注解在成員屬性(字段)上,但是@Validated不行
- 由于第2點(diǎn)的不同,將導(dǎo)致@Validated不能做嵌套校驗(yàn)
- @valid只能用在controller。@Validated可以用在其他被spring管理的類上父能。
對(duì)于第4點(diǎn)的不同神凑,體現(xiàn)了@validated注解其實(shí)又更實(shí)用的功能。那就是@validated可以用在普通bean的方法校驗(yàn)上法竞。
@validated的使用注意點(diǎn)
1 @validated和@valid都可以用在controller層的參數(shù)前面耙厚,但這只能在controller層生效强挫。
2 @validated如果要開啟方法驗(yàn)證。注解應(yīng)該打在類上薛躬,而不是方法參數(shù)上俯渤。
3 方法驗(yàn)證模式下,被jsr303標(biāo)準(zhǔn)的注解修飾的可以是方法參數(shù)也可以是返回值型宝,類似如下
public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
4 @validated不支持嵌套驗(yàn)證八匠。所以jsr303標(biāo)準(zhǔn)的注解修飾的對(duì)象只能基本類型和包裝類型。其他類型只能做到檢測(cè)是否為空趴酣,
對(duì)于對(duì)象里面的jsr303標(biāo)準(zhǔn)的注解修飾的屬性梨树,不支持驗(yàn)證。
如何實(shí)現(xiàn)
看MethodValidationPostProcessor類繼承圖譜
實(shí)現(xiàn)了InitializingBean和BeanPostProcessor岖寞。所以我們重點(diǎn)看生命周期的2個(gè)節(jié)點(diǎn)方法抡四。
BeanPostProcessor.postProcessAfterInitialization
InitializingBean.afterPropertiesSet
根據(jù)生命周期的順序,先執(zhí)行afterPropertiesSet方法仗谆。
MethodValidationPostProcessor.afterPropertiesSet
->Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);//創(chuàng)建了切入點(diǎn)
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));//創(chuàng)建了切面指巡。
然后再看postProcessAfterInitialization。實(shí)現(xiàn)是在MethodValidationPostProcessor的祖父類AbstractAdvisingBeanPostProcessor上
AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization
->if (bean instanceof Advised)//如果被攔截的bean已經(jīng)是代理類了隶垮。
if (this.beforeExistingAdvisors)//并且beforeExistingAdvisors=true的時(shí)候藻雪,
advised.addAdvisor(0, this.advisor);//把當(dāng)前切面放到代理類切面的第一位±晖蹋可以想到beforeExistingAdvisors參數(shù)的作用是為了保證驗(yàn)證切面優(yōu)先于其他的切面勉耀。
//并且這里我們也能得知,代理類的切面可能不止1個(gè)蹋偏,相當(dāng)于代理類里面還存在攔截器鏈一樣便斥。//后文會(huì)去看這塊的源碼
->if (isEligible(bean, beanName))//如果被攔截的bean不是一個(gè)代理類。先校驗(yàn)一下這個(gè)類是否有資格添加上 validated的切面暖侨。原理就是掃描這個(gè)類上面是否有@validated注解
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
proxyFactory.addAdvisor(this.advisor);
return proxyFactory.getProxy(getProxyClassLoader());//創(chuàng)建代理椭住。
->createAopProxy().getProxy(classLoader);
->ProxyCreatorSupport.createAopProxy
->getAopProxyFactory().createAopProxy(this)
->DefaultAopProxyFactory.createAopProxy
->if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {//如果該類有接口或者是代理類了,則直接使用jdk動(dòng)態(tài)代理
return new JdkDynamicAopProxy(config);}
return new ObjenesisCglibAopProxy(config);//否則使用cglib動(dòng)態(tài)代理
->JdkDynamicAopProxy.getProxy
->Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);//這一步找到目標(biāo)類的所有接口字逗。
//另外根據(jù)配置決定是否把Advised接口也加進(jìn)去京郑。加進(jìn)去就意味著代理類也是Advised的實(shí)現(xiàn)類。那這一步顯示是加上了Advised接口
->Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//看到調(diào)用jdk動(dòng)態(tài)代理的api了葫掉。
小結(jié):
1 什么時(shí)候創(chuàng)建的切面
在afterPropertiesSet方法中創(chuàng)建切面些举。
這里我就有一個(gè)疑問了,是不是實(shí)現(xiàn)InitializingBean的類都優(yōu)先被加載呢俭厚?不然怎么提前把切面創(chuàng)建出來呢户魏?2 怎么掃描注解的
實(shí)現(xiàn)BeanPostProcessor的方法,可以攔截到所有bean3 什么時(shí)候給目標(biāo)類做代理的
也是在BeanPostProcessor.postProcessAfterInitialization方法中做的,攔截了bean之后叼丑,就檢測(cè)是否類似打上了@validated注解,
如果有就創(chuàng)建代理关翎,4 如果目標(biāo)類已經(jīng)代理了,怎么辦
如果已經(jīng)是代理了鸠信,就直接添加切面纵寝。如果想優(yōu)先使用驗(yàn)證切面,則需要設(shè)置優(yōu)先級(jí)為0.那么怎么設(shè)置優(yōu)先級(jí)呢星立?
直接把beforeExistingAdvisors屬性設(shè)置為true即可爽茴。5 代理類的攔截器鏈?zhǔn)窃趺磳?shí)現(xiàn)的呢?
根據(jù)jdk動(dòng)態(tài)代理的知識(shí)绰垂,會(huì)動(dòng)態(tài)的給UserService 類創(chuàng)建一個(gè)實(shí)現(xiàn)類(代理類)并實(shí)現(xiàn)了接口中的所有方法室奏,當(dāng)調(diào)用接口中的方法其實(shí)先經(jīng)過代理類。
代理類在把請(qǐng)求傳遞給InvocationHandler實(shí)例劲装。估計(jì)InvocationHandler實(shí)現(xiàn)里面有維護(hù)著一個(gè)攔截器鏈胧沫,那么InvocationHandler是怎么設(shè)置的呢?
接著看源碼
->JdkDynamicAopProxy.getProxy
->Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//看到調(diào)用jdk動(dòng)態(tài)代理的api了占业。
//通過this可以看到InvocationHandler實(shí)例其實(shí)就是JdkDynamicAopProxy類琳袄。
JdkDynamicAopProxy.invoke(Object proxy, Method method, Object[] args)
->List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);//原來InvocationHandler
//保存著切面配置信息AdvisedSupport advised。advised里面存儲(chǔ)了切面集合纺酸,下標(biāo)代表了優(yōu)先級(jí)。這個(gè)是什么時(shí)候傳遞進(jìn)來的呢址否?
//是在創(chuàng)建proxyFactory的時(shí)候餐蔬,把配置信息傳遞給proxyFactory的 proxyFactory.addAdvisor(this.advisor)。這個(gè)時(shí)候驗(yàn)證切面就進(jìn)到了proxyFactory中去了佑附。
//創(chuàng)建代理InvocationHandler樊诺,又把切面配置信息傳遞過去的。
->AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice
->List<Object> chain = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass);//把a(bǔ)dvised對(duì)象向下傳遞
->DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, @Nullable Class<?> targetClass)//這一步主要是遍歷AdvisedSupport
//對(duì)象的切面集合音同,即AdvisedSupport.advisors词爬。把所有可以攔截這個(gè)方法的切面都裝到一個(gè)集合中去。
->MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);//組裝成一個(gè)帶有鏈的執(zhí)行器
->retVal = invocation.proceed();//方法內(nèi)部攔截器數(shù)組下標(biāo)為0的攔截器調(diào)用invoke权均。
| ->Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);//currentInterceptorIndex初始默認(rèn)是-1,下標(biāo)+1
| InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
^ return dm.interceptor.invoke(this);//如果驗(yàn)證攔截器優(yōu)先調(diào)用的話顿膨,則這一步一定會(huì)進(jìn)到MethodValidationInterceptor
| ->MethodValidationInterceptor.invoke(MethodInvocation invocation)
| ->invocation.proceed()---|
|---------<--------<-----------<--------|