@validated注解實現


 

 

用法


public interface UserService {
    public  UserModel get2( Integer uuid) ;
}
@Validated      //① 告訴MethodValidationPostProcessor此Bean需要開啟方法級別驗證支持
@Component
public class UserServiceImpl implement UserService  {
    public @NotNull UserModel get2(@NotNull @Min(value = 1) Integer uuid) { //②聲明前置條件/后置條件
        //獲取 User Model
        UserModel user = new UserModel(); //此處應該從數據庫獲取
        if(uuid > 100) {//方便后置添加的判斷(此處假設傳入的uuid>100 則返回null)
            return null;
        }
        return user;
    }
}

<!--注冊方法驗證的后處理器-->
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

@validated和@valid不同點

在spring項目中,@validated和@valid功能很類似,都可以在controller層開啟數據校驗功能。
但是@validated和@valid又不盡相同。有以下不同點:

  1. 分組
  2. 注解地方,@Valid可以注解在成員屬性(字段)上,但是@Validated不行
  3. 由於第2點的不同,將導致@Validated不能做嵌套校驗
  4. @valid只能用在controller。@Validated可以用在其他被spring管理的類上。
    對於第4點的不同,體現了@validated注解其實又更實用的功能。那就是@validated可以用在普通bean的方法校驗上。

@validated的使用注意點

1 @validated和@valid都可以用在controller層的參數前面,但這只能在controller層生效。
2 @validated如果要開啟方法驗證。注解應該打在類上,而不是方法參數上。
3 方法驗證模式下,被jsr303標准的注解修飾的可以是方法參數也可以是返回值,類似如下
public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
4 @validated不支持嵌套驗證。所以jsr303標准的注解修飾的對象只能基本類型和包裝類型。其他類型只能做到檢測是否為空,
對於對象里面的jsr303標准的注解修飾的屬性,不支持驗證。

如何實現

看MethodValidationPostProcessor類繼承圖譜

 

 
clipboard.png

 

實現了InitializingBean和BeanPostProcessor。所以我們重點看生命周期的2個節點方法。
BeanPostProcessor.postProcessAfterInitialization
InitializingBean.afterPropertiesSet
根據生命周期的順序,先執行afterPropertiesSet方法。

MethodValidationPostProcessor.afterPropertiesSet
    ->Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);//創建了切入點
      this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));//創建了切面。

然后再看postProcessAfterInitialization。實現是在MethodValidationPostProcessor的祖父類AbstractAdvisingBeanPostProcessor上

AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization
    ->if (bean instanceof Advised)//如果被攔截的bean已經是代理類了。
        if (this.beforeExistingAdvisors)//並且beforeExistingAdvisors=true的時候,
            advised.addAdvisor(0, this.advisor);//把當前切面放到代理類切面的第一位。可以想到beforeExistingAdvisors參數的作用是為了保證驗證切面優先於其他的切面。
                                                //並且這里我們也能得知,代理類的切面可能不止1個,相當於代理類里面還存在攔截器鏈一樣。//后文會去看這塊的源碼
        ->if (isEligible(bean, beanName))//如果被攔截的bean不是一個代理類。先校驗一下這個類是否有資格添加上 validated的切面。原理就是掃描這個類上面是否有@validated注解
            ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
            proxyFactory.addAdvisor(this.advisor);
            return proxyFactory.getProxy(getProxyClassLoader());//創建代理。
                ->createAopProxy().getProxy(classLoader);
                    ->ProxyCreatorSupport.createAopProxy
                        ->getAopProxyFactory().createAopProxy(this)
                            ->DefaultAopProxyFactory.createAopProxy
                                ->if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {//如果該類有接口或者是代理類了,則直接使用jdk動態代理
                                       return new JdkDynamicAopProxy(config);}
                                   return new ObjenesisCglibAopProxy(config);//否則使用cglib動態代理
                    ->JdkDynamicAopProxy.getProxy
                        ->Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);//這一步找到目標類的所有接口。
                                                //另外根據配置決定是否把Advised接口也加進去。加進去就意味着代理類也是Advised的實現類。那這一步顯示是加上了Advised接口
                        ->Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//看到調用jdk動態代理的api了。

小結:

  • 1 什么時候創建的切面
    在afterPropertiesSet方法中創建切面。
    這里我就有一個疑問了,是不是實現InitializingBean的類都優先被加載呢?不然怎么提前把切面創建出來呢?

  • 2 怎么掃描注解的
    實現BeanPostProcessor的方法,可以攔截到所有bean

  • 3 什么時候給目標類做代理的
    也是在BeanPostProcessor.postProcessAfterInitialization方法中做的,攔截了bean之后,就檢測是否類似打上了@validated注解,
    如果有就創建代理,

  • 4 如果目標類已經代理了,怎么辦
    如果已經是代理了,就直接添加切面。如果想優先使用驗證切面,則需要設置優先級為0.那么怎么設置優先級呢?
    直接把beforeExistingAdvisors屬性設置為true即可。

  • 5 代理類的攔截器鏈是怎么實現的呢?
    根據jdk動態代理的知識,會動態的給UserService 類創建一個實現類(代理類)並實現了接口中的所有方法,當調用接口中的方法其實先經過代理類。
    代理類在把請求傳遞給InvocationHandler實例。估計InvocationHandler實現里面有維護着一個攔截器鏈,那么InvocationHandler是怎么設置的呢?
    接着看源碼

->JdkDynamicAopProxy.getProxy
    ->Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//看到調用jdk動態代理的api了。
                                                                //通過this可以看到InvocationHandler實例其實就是JdkDynamicAopProxy類。
JdkDynamicAopProxy.invoke(Object proxy, Method method, Object[] args)
    ->List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);//原來InvocationHandler
        //保存着切面配置信息AdvisedSupport advised。advised里面存儲了切面集合,下標代表了優先級。這個是什么時候傳遞進來的呢?
        //是在創建proxyFactory的時候,把配置信息傳遞給proxyFactory的 proxyFactory.addAdvisor(this.advisor)。這個時候驗證切面就進到了proxyFactory中去了。
        //創建代理InvocationHandler,又把切面配置信息傳遞過去的。
        ->AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice
            ->List<Object> chain = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass);//把advised對象向下傳遞
                ->DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, @Nullable Class<?> targetClass)//這一步主要是遍歷AdvisedSupport
                                                                                                    //對象的切面集合,即AdvisedSupport.advisors。把所有可以攔截這個方法的切面都裝到一個集合中去。
           ->MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);//組裝成一個帶有鏈的執行器
               ->retVal = invocation.proceed();//方法內部攔截器數組下標為0的攔截器調用invoke。
               |    ->Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);//currentInterceptorIndex初始默認是-1,下標+1
               |      InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
               ^      return dm.interceptor.invoke(this);//如果驗證攔截器優先調用的話,則這一步一定會進到MethodValidationInterceptor
               |          ->MethodValidationInterceptor.invoke(MethodInvocation invocation)
               |              ->invocation.proceed()---|
               |---------<--------<-----------<--------|
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM