基於個人理解的springAOP部分原始碼分析,內含較多原始碼,慎入

J,IAT發表於2020-07-29

本文原始碼較多,講述一些個人對spring中AOP程式設計的一個原始碼分析理解,只代表個人理解,希望能和大家進行交流,有什麼錯誤也渴求指點!!!接下來進入正題

AOP的實現,我認為簡單的說就是利用代理模式,對目標方法所在的類進行封裝代理。請求目標方法時,是直接請求代理物件,再根據使用者指定的通知(切點),在代理物件中進行操作,到了該使用目標方法的時候,呼叫代理物件中包裝的真正目標方法完成,以實現面向切面程式設計,以下對兩個問題進行一個分析:

  • 代理物件什麼時候被建立
  • 切面類我們定義的切點資訊是怎麼載入的
    • 找到在何處進行掃描
    • 探究如何進行掃描

代理物件是什麼時候被建立的?

這裡是通過name來獲取bean的情況,注意使用type的方式來獲取bean進行除錯,就會和本文有所出入
現在從Main方法出發

//1.建立IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.從IOC中獲取bean例項
ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculatorImpl");
  • 我們進入getBean的程式碼檢視,發現bean是直接在一個名為singletonObjects的concurrentHashMap(ioc容器)中取出來的,取出時就已經是一個proxy物件了,可以證明,是在初始化例項的時候就建立了

  • 我們知道spring會在程式啟動的時候,初始化ioc容器,而對於我們指定的切面類,在初始化例項時,就將找到我們指定的類,對其進行建立代理,代理物件建立後,放置到ioc容器中

  • 具體的流程是在初始化到我們指定的物件時,他會先建立出一個未代理的例項
    該例項會經過一個applyBeanPostProcessorsAfterInitialization方法,7個spring的後置處理器進行遍歷,如果該類符合某個後置處理器的條件,則會被後置處理器載入,而我們aop的類會被(AnnotationAwareAspectJAutoProxyCreator)後置處理器處理,如下為7個後置處理器

  • 進入到該後置處理器我們會發現,他會通過判斷這個類是否註釋了切面程式設計的標記,如果註釋了則進行處理,也就是我們的註解起了作用,這裡是經過了AnnotationAwareAspectJAutoProxyCreator後置處理器的處理,以下是AnnotationAwareAspectJAutoProxyCreator處理器的applyBeanPostProcessorsAfterInitialization初始化方法

    //applyBeanPostProcessorsAfterInitialization方法的原始碼
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
        Object result = existingBean;
    
        Object current;
        for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) {
            BeanPostProcessor processor = (BeanPostProcessor)var4.next();
            //processor=AnnotationAwareAspectJAutoProxyCreator時,會進入postProcessAfterInitialization方法
            current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
        }
        return result;
    }
    
    //進入AnnotationAwareAspectJAutoProxyCreator後置處理器的操作,通過除錯我們看到,在wrapIfNecessary方法中進行了代理
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            //判斷是否需要代理
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                //代理
                return this.wrapIfNecessary(bean, beanName, cacheKey);
            }
    }
    
    //該方法是真正對proxy進行代理的方法
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
            //獲取切點資訊
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if (specificInterceptors != DO_NOT_PROXY) {
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                //建立了代理物件
                Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                this.proxyTypes.put(cacheKey, proxy.getClass());
                //返回
                return proxy;
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }
    
  • 後置處理器的處理方式,是在一個(postProcessAfterInitialization)方法中進行的處理,通過判斷指定是使用jdbc的動態代理建立proxy還是通過cglib,處理後返回到變數中,完成後新增到ioc容器中,完成初始化,之後呼叫時都是已經代理過的例項,就可以進行切面程式設計了

//最終是呼叫了DefaultAopProxyFactory類的createAopProxy來實現代理
//可以看到這裡根據一些配置條件來判斷我們要建立的代理是jdk動態代理還是cglib
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
        //建立返回
        return new JdkDynamicAopProxy(config);
    } else {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
        } else {
            //建立返回
            return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
        }
    }
}

也就是說,spring在初始化容器的時候,會為@Aspect註解的類進行一個代理操作,而其中主要起作用的是7個後置處理器中的AnnotationAwareAspectJAutoProxyCreator處理器對其進行了代理

切點資訊是如何獲取、封裝,然後儲存在List中的

先找到他在何處進行的掃描

以上的建立代理過程中,我們知道了如下程式碼是獲得了切點資訊的一個語句

//該方法中呼叫的時返回值為List<Advisor>的方法,在返回的時候將其轉化為Object[]型別,所以以下程式碼的返回值就程式設計List<Advisor>,也說明了Advisor就是封裝了pointcut資訊的物件
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);

進入其中,往裡追程式碼,追到以下位置

protected List<Advisor> findCandidateAdvisors() {
    List<Advisor> advisors = super.findCandidateAdvisors();
    if (this.aspectJAdvisorsBuilder != null) {
        //我們可以看到新增Advisor於buildAspectJAdvisors()有關
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }

繼續往裡追,我們看到在buildAspectJAdvisors方法中有這麼一句程式碼

  List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);

觀察advisorsCache,可以知道,這是一個map集合,顯然他以我們@AspectJ註解的類名為key,切點註解的資訊集合為值

我們發現只是從advisorsCache這個map中取出來的,也就是在這之前advisorsCache就已經賦值了,於是我們應該追溯advisorsCache物件的初始化,這裡利用一個小技巧,當我們要追溯一個map或者其他集合的構建時,可以通過查詢關鍵字即advisorsCache.put這樣的方式來追溯到位置,於是我們發現同樣在buildAspectJAdvisors方法中我們能看到此處有對advisorsCache的初始化,程式碼分析如下

//BeanFactoryAspectJAdvisorsBuilder類中的
public List<Advisor> buildAspectJAdvisors() {
    //aspectBeanNames擁有我們註釋了AspectJ註解的類名
    List<String> aspectNames = this.aspectBeanNames;
    //我們發現第一獲取時,aspectNames為空,會進入一個執行緒安全的程式碼塊進行掃描,猜測這是一個雙重檢驗實現懶載入,需要的時候才去掃描註解
    if (aspectNames == null) {
        synchronized(this) {
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new ArrayList();
                List<String> aspectNames = new ArrayList();
                //呼叫了工具來得到所有的beanNames
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
                String[] var18 = beanNames;
                int var19 = beanNames.length;
                //遍歷所有的bean
                for(int var7 = 0; var7 < var19; ++var7) {
                    String beanName = var18[var7];
                    //isEligibleBean -- 具備條件
            //protected boolean isEligibleBean(String beanName) {
            // return AnnotationAwareAspectJAutoProxyCreator.this.isEligibleAspectBean(beanName);
       	    // }
                    if (this.isEligibleBean(beanName)) {
                        Class<?> beanType = this.beanFactory.getType(beanName);
                        //this.advisorFactory.isAspect(beanType)校驗是否有這個註解
                        //    public boolean isAspect(Class<?> clazz) {
        			   //return this.hasAspectAnnotation(clazz) && !this.compiledByAjc(clazz);
    				  //}
                        if (beanType != null && this.advisorFactory.isAspect(beanType)) {
                            //將類名儲存
                            aspectNames.add(beanName);
                            //AspectMetadata應該是判斷該beanName對應的類有沒有一個註解@Aspect,主要是在AspectMetadata的AjType物件的getPerClause()中進行一個判斷,如果是true,則new PerClauseImpl(PerClauseKind.SINGLETON)) 返回,則可以通過以下的if判斷
                            AspectMetadata amd = new AspectMetadata(beanType, beanName);
                           if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                //MetadataAwareAspectInstanceFactory就是把beanFactory和當前得到的beanName封裝在一起
                               MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                                //此處得到Advisor結果集,Advisor主要是封裝了每個方法對應的連線點
                                List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                                if (this.beanFactory.isSingleton(beanName)) {
                                    //滿足條件之後新增到advisorsCache中,
                                    this.advisorsCache.put(beanName, classAdvisors);
                                } else {
                                    this.aspectFactoryCache.put(beanName, factory);
                                }
                               //同時新增到advisors中,滿足第一次呼叫輸出
                                advisors.addAll(classAdvisors);
                            } else {
                                if (this.beanFactory.isSingleton(beanName)) {
                                    throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton");
                                }

                                MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                                this.aspectFactoryCache.put(beanName, factory);
                                advisors.addAll(this.advisorFactory.getAdvisors(factory));
                            }}}}
                this.aspectBeanNames = aspectNames;
                //返回
                return advisors;
            }
     }
     if (aspectNames.isEmpty()) {
        return Collections.emptyList();
         //如果不為空
    } else {
         //建立一個封裝了切點資訊的advisor物件集合
        List<Advisor> advisors = new ArrayList();
        Iterator var3 = aspectNames.iterator();

        while(var3.hasNext()) {
            String aspectName = (String)var3.next();
            //只要不為空,在advisorsCache就能拿到aspectName對應的List<Advisor>,也就是advisorsCache這個map中已經存好了我們的pointcut,只是將他賦給advisors然後返回而已,也即是我們上一步尋找切點資訊來源時的程式碼
            List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);
            if (cachedAdvisors != null) {
                advisors.addAll(cachedAdvisors);
            } else {
                MetadataAwareAspectInstanceFactory factory = (MetadataAwareAspectInstanceFactory)this.aspectFactoryCache.get(aspectName);
                advisors.addAll(this.advisorFactory.getAdvisors(factory));
            }
        }
        return advisors;
    }
}

也就是說在buildAspectJAdvisors()處,spring是使用了synchronized程式碼塊以及雙重校驗來保證關於切點資訊掃描的程式碼塊在任何情況下都只執行一次,也就是執行緒安全的,而且在此處是一個懶載入的效果,第一次呼叫就會進入程式碼塊執行掃描並返回結果,第二次就直接從cachedAdvisors獲取了,完成的是掃描所有的bean,會對我們@Aspect註解進行識別,解析,將類中每個切點的型別和切點的目標和方法封裝在一起,得到多個切點物件,然後返回

以上為我認為的spring掃描切點資訊的原始碼位置

接下來看看他是怎麼掃描的

為了方便以下程式碼的閱讀,我先將封裝結構貼在這裡,以下為Advisor對切點資訊的封裝結構:一個Advisor封裝的是一個切點,一個bean有多個Advisor

接下來進入程式碼分析,由上可以知道Advisor物件是封裝pointcut資訊的,那麼我們就追進getAdvisors()方法來看看

//獲取Advisor結果集,摘取部分程式碼我們來看看
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    ……………………
	//var6為該bean的所有方法,迴圈得到方法上的註解資訊
    while(var6.hasNext()) {
            Method method = (Method)var6.next();
        	//傳入方法得物件
            Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
            if (advisor != null) {
                advisors.add(advisor);
     }
        ……………………
 }
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {
    	//猜測這是一個判斷,檢查的或許和Aspect有關
       this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
    	//可以看出來,getPointcut方法就是封裝切點資訊的,進入這個方法
       AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
        return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
    }

	private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    //findAspectJAnnotationOnMethod得到方法上的註解資訊,也就是我們指定的連線點的處理方法,在這個方法中將aspectJ支援的切面通知型別逐個遍歷,看看method中的註解是哪一個,並提取出他的資訊,內部程式碼為:
    //     A result = AnnotationUtils.findAnnotation(method, toLookFor);
    //    return result != null ? new AbstractAspectJAdvisorFactory.AspectJAnnotation(result) : null;
    //		result為 method.getDeclaredAnnotation(annotationType)的返回值,即result封裝的是我們在類上的A切點註解,將其作為引數構建出AspectJAnnotation物件並返回,也就是說AspectJAnnotation物件中就包含了切點註解的資訊
    //		而進入AspectJAnnotation物件的構造器中,我們發覺呼叫this.pointcutExpression = this.resolveExpression(annotation);完成對excution表示式的解析,內部程式碼比較容易看懂,這裡不做記錄
    AspectJAnnotation<?> aspectJAnnotation = 
        AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    } else {
        AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
        //在這裡設定了pointcut資訊,execution表示式在建立aspectJAnnotation的時候已經賦值了
        ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
        if (this.beanFactory != null) {
            ajexp.setBeanFactory(this.beanFactory);
        }
        //返回包含了一個切點的AspectJExpressionPointcut物件
        return ajexp;
    }
}

受本人水平有限,關於掃描只能分析到這裡,之後會繼續更新,有錯誤希望大家可以評論指點,謝謝大佬~~

相關文章