Spring AOP 注解配置 源碼學習

上篇Spring AOP學習中已經基本介紹了AOP是如何使用的,本文章來說說AOP注解方法的源碼細節
先提幾個問題,在接下來的源碼學習中發現答案

  • <aop:aspectj-autoproxy />的工作原理是什么,能帶來什么作用
  • @Aspect是在什么時候被操作的
  • 各個切入點是按照什么樣的順序執行
  • 切入點是如何和對應的bean綁定在一起的

具體的demo可以看Spring AOP學習#注解

XML配置解析

AOP的配置最簡單的方法就是在xml中加入<aop:aspectj-autoproxy />,應該很清楚spring是選擇什么命名空間去解析了,直接定位到AopNamespaceHandler 類,而且定位的是AspectJAutoProxyBeanDefinitionParser解析器,如果這點有什么疑問的話,最好是再看看spring xml的bean提取 源碼學習,看完就懂了為什么會直接定位到該解析器

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        // AspectJAutoProxyBeanDefinitionParser 是我們這次關注的類
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
}

AspectJAutoProxyBeanDefinitionParser 類

class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
    
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
      // 可以生成AnnotationAwareAspectJAutoProxyCreator類的beandefinition
      // 并注冊到IOC容器中
        extendBeanDefinition(element, parserContext);
        // 擴展該bean的屬性信息
        return null;
    }

    private void extendBeanDefinition(Element element, ParserContext parserContext) {
        BeanDefinition beanDef =
                parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
      // 該bean其實就是上面所說的AnnotationAwareAspectJAutoProxyCreator的beandefinition
        if (element.hasChildNodes()) {
            addIncludePatterns(element, parserContext, beanDef);
            // 如果包含了子節點可以添加額外的includePattern
        }
    }

    private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
        ManagedList<TypedStringValue> includePatterns = new ManagedList<TypedStringValue>();
        NodeList childNodes = element.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node node = childNodes.item(i);
            if (node instanceof Element) {
                Element includeElement = (Element) node;
                TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
                // 獲取子屬性key為name的值
                valueHolder.setSource(parserContext.extractSource(includeElement));
                includePatterns.add(valueHolder);
            }
        }
        if (!includePatterns.isEmpty()) {
            includePatterns.setSource(parserContext.extractSource(element));
            beanDef.getPropertyValues().add("includePatterns", includePatterns);
            // 給該beandefinition賦屬性值,includePatterns
        }
    }
}

如上述代碼中的AnnotationAwareAspectJAutoProxyCreator類創建中,如果跳進去能夠看到還可以為該類設置proxy-target-classexpose-proxy兩個boolean屬性值。
也可以設置子屬性值name,例如下面的設置

<aop:aspectj-autoproxy proxy-target-class="false" expose-proxy="true">
   <aop:include name="******" />
</aop:aspectj-autoproxy>

這樣就清楚了這個xml配置的原理是如何,其實就是新添加了一個beandefinition而已。

Aspect 掃描和實例化

spring正常的注冊的bean,如果沒有加@Aspect注解也是沒有用的,那么就來學習下是在什么時候被掃描到,以及后續的操作是什么。

如果大概看了AnnotationAwareAspectJAutoProxyCreator類的相關內容,可以知道AOP所有的aspect以及pointCut都存儲在該對象中,所以掃描所有的注解@Aspect類,肯定是在AnnotationAwareAspectJAutoProxyCreator類的實例化之后

在refresh()代碼中,在實例化具體的bean之前,已經完成了spring層面的bean實例


image.png

在接下來對org.springframework.context.event.internalEventListenerProcessor對象實例化的過程中會完成對@aspect類的掃描操作,并把相關信息注入到上圖圈住的AnnotationAwareAspectJAutoProxyCreator類中。

隨著代碼調試到AbstractAutoProxyCreator文件

AbstractAutoProxyCreator 類

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {  
   Object cacheKey = getCacheKey(beanClass, beanName);
   // 從當前的cache容器獲取到bean的name
    if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
            // 該advisedBeans已經包含了該key,則直接退出不考慮
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
           // 該bean不符合所有獲取到的切面,設置為false
           // shouldSkip方法看下面的代碼
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

   // 所有的注解信息已經獲取到了
    // 具體通過代理類生成一個對象信息
    if (beanName != null) {
        TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            this.targetSourcedBeans.add(beanName);
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
            // 利用ProxyFactory 生成一個新的對象
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
    }

    return null;
}

AspectJAwareAdvisorAutoProxyCreator 類

protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 獲取所有的advisor,也就是PointCut,具體細節可看下面的函數執行過程代碼
    for (Advisor advisor : candidateAdvisors) {
        if (advisor instanceof AspectJPointcutAdvisor) {
            if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
                return true;
            }
        }
    }
    return super.shouldSkip(beanClass, beanName);
}

BeanFactoryAdvisorRetrievalHelper 類

這個類是AnnotationAwareAspectJAutoProxyCreator的一個對象

    public List<Advisor> findAdvisorBeans() {
        String[] advisorNames = null;
        synchronized (this) {
            advisorNames = this.cachedAdvisorBeanNames;
            if (advisorNames == null) {
              // 還沒準備好,首次進入該函數時,為null
                advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Advisor.class, true, false);
               // 獲取到IOC容器中,所有類的類型是Advisor.class的
               // 當然就我們當前的demo而言肯定是沒有的,只有加上了Advisor.class注解的類
               // 會繼續進入到**buildAspectJAdvisors函數**中去獲取到注解為Advisor.class的類
                this.cachedAdvisorBeanNames = advisorNames;
            }
        }
        if (advisorNames.length == 0) {
            return new LinkedList<Advisor>();
        }

        List<Advisor> advisors = new LinkedList<Advisor>();
        for (String name : advisorNames) {
            if (isEligibleBean(name)) {
                if (this.beanFactory.isCurrentlyInCreation(name)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Skipping currently created advisor '" + name + "'");
                    }
                }
                else {
                    try {
                        advisors.add(this.beanFactory.getBean(name, Advisor.class));
                    }
                    catch (BeanCreationException ex) {
                        Throwable rootCause = ex.getMostSpecificCause();
                        if (rootCause instanceof BeanCurrentlyInCreationException) {
                            BeanCreationException bce = (BeanCreationException) rootCause;
                            if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Skipping advisor '" + name +
                                            "' with dependency on currently created bean: " + ex.getMessage());
                                }
                                // Ignore: indicates a reference back to the bean we're trying to advise.
                                // We want to find advisors other than the currently created bean itself.
                                continue;
                            }
                        }
                        throw ex;
                    }
                }
            }
        }
        return advisors;
    }
public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = this.aspectBeanNames;

    if (aspectNames == null) {
       // 第一次進入,確實為null
        synchronized (this) {
            aspectNames = this.aspectBeanNames;
            // spring中為了確保線程安全,存在大量的類似double-check的代碼
            if (aspectNames == null) {
                List<Advisor> advisors = new LinkedList<Advisor>();
                aspectNames = new LinkedList<String>();
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Object.class, true, false);
                // 獲取IOC所有類型為Object的類名稱(肯定包含了注解@Aspect的相關類)
                for (String beanName : beanNames) {
                    if (!isEligibleBean(beanName)) {
                       // 類名和includePatterns正則匹配不通過,則跳過,起到一個過濾的作用
                       // 匹配的是類似于com.demo.*這樣的類名稱
                       // 和上面說的<aop:include name="******" />相關聯
                        continue;
                    }
                    
                    Class<?> beanType = this.beanFactory.getType(beanName);
                    if (beanType == null) {
                       // 獲取其類型,如果為null,無效,跳過
                        continue;
                    }
                    if (this.advisorFactory.isAspect(beanType)) {
                       // 注解包含了@Aspect 并且沒有被AspectJ編譯(字段以ajc$開頭)的類
                        aspectNames.add(beanName);
                        // 本demo的beanName就是animalAop
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                            MetadataAwareAspectInstanceFactory factory =
                                    new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                            // 獲取該類所有的方法,例如@Before、@After等
                            // 取到該類所有的方法,按照Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class 的順序排序
                            // 依次遍歷,如果發現了上述的注解,則保存pointCut到一個容器中
                            
                            // 獲取該類所有的共有方法,如果符合要求,也保存到容器中,最后返回到List中
                            
                            if (this.beanFactory.isSingleton(beanName)) {
                                this.advisorsCache.put(beanName, classAdvisors);
                                // 如果是單例,則保存到cache共,否則保存到工廠cache中
                            }
                            else {
                                this.aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        }
                        else {
                            // Per target or per this.
                            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));
                        }
                    }
                }
                // 這里給aspectNames 賦值
                // 需要注意到一旦賦值了,就不會為空,則不會再去掃描所有的bean,得到全部的AOP信息
                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
    }
    
    // 能運行到這里的肯定是第二次執行,獲取到需要的AOP的pointCut信息
    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    }
    List<Advisor> advisors = new LinkedList<Advisor>();
    for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        }
        else {
            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}

最后實例化代理對象AnimalImpl對象,經過調試來到了


image.png

AbstractAutoProxyCreator 文件

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // 得到相關的advice
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        // 這一步就是最關鍵的步驟,最后生成代理類
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

如下圖,調試中生成了最終的代理類


image.png

至此,整個的代理類的實現過程就全部完成了。

總結下整個AOP注解操作的過程

1、先利用xml配置,添加AnnotationAwareAspectJAutoProxyCreator類到IOC容器中
2、在實例化各種beanpostprocessor的時候,掃描IOC所有的bean,如果配置了includePattern屬性,還需要對IOC容器的所有bean進行正則匹配,通過的才會進行接下來的操作
3、所有合適的bean遍歷找到注解為@Aspect的類,輪詢bean的方法,如果包含了Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class的注解方法,也需要收集起來,最后形成kv對,添加到AnnotationAwareAspectJAutoProxyCreator的cache容器中
4、實例化被代理的類之前需要實例化本身
5、從AnnotationAwareAspectJAutoProxyCreator存儲的所有advisor匹配出符合規則的advisor

具體可看AopUtils抽象類的findAdvisorsThatCanApply 方法

6、最后生成一個ProxyFactory,添加上述得到的各種數據,生成代理類

再來回答文章前提到的幾個問題

  • <aop:aspectj-autoproxy />的工作原理是什么,能帶來什么作用

無需再強調,如果還是無法理解,可以仔細閱讀spring xml的bean提取 源碼學習,帶來的作用就是新增了一個管理Aspect的類,所有的advisor都是存放在這個類中

  • @Aspect是在什么時候被操作的

beanpostprocessor實例化的時候,特指org.springframework.context.event.internalEventListenerProcessor類的實例化時,會去掃描所有的bean,檢查是否存在@Aspect注解,如果有該注解,則添加到AnnotationAwareAspectJAutoProxyCreator中

  • 各個切入點是按照什么樣的順序執行

切入點是按照Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class自定義的排序規則sort的操作的

  • 切入點是如何和對應的bean綁定在一起的

相互獨立的,只有在bean具體生成的時候從AnnotationAwareAspectJAutoProxyCreator中獲取到合適的切面以及切點信息,最后生成代理類。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容