Spring AOP實現(xiàn)原理

通過上一篇文章Spring Bean的創(chuàng)建過程及相關(guān)擴展點的介紹豫领,我們知道getBean()創(chuàng)建Bean實例的過程赞枕,有以下幾個擴展點:

  • Bean實例創(chuàng)建之前實現(xiàn)InstantiationAwareBeanPostProcessor接口
  • Bean實例創(chuàng)建成功后,未設置屬性時怎憋,立馬創(chuàng)建一個工廠方法姨丈。可通過實現(xiàn)SmartInstantiationAwareBeanPostProcessor接口對Bean增強
  • Bean初始化的生命周期中埃难,實現(xiàn)BeanPostProcessor接口對Bean增強

SpringBoot如何實現(xiàn)Aop:

1.先從spring-boot-autoconfigure包下面的/META-INF/spring.factories開始莹弊,可以發(fā)現(xiàn)AopAutoConfiguration這個類涤久,說明在引入spring-boot-autoconfigure這個jar包后,就會自動加載AopAutoConfiguration忍弛。

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Advice.class)
    static class AspectJAutoProxyingConfiguration {

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
                matchIfMissing = false)
        static class JdkDynamicAutoProxyConfiguration {

        }

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
                matchIfMissing = true)
        static class CglibAutoProxyConfiguration {

        }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.aspectj.weaver.Advice")
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
            matchIfMissing = true)
    static class ClassProxyingConfiguration {

        ClassProxyingConfiguration(BeanFactory beanFactory) {
            if (beanFactory instanceof BeanDefinitionRegistry) {
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
        }
    }
}

主要是看EnableAspectJAutoProxy這個注解响迂,無論如何,這個注解都會生效

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

    /**
     * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
     * to standard Java interface-based proxies. The default is {@code false}.
     */
    boolean proxyTargetClass() default false;

    /**
     * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
     * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
     * Off by default, i.e. no guarantees that {@code AopContext} access will work.
     * @since 4.3.1
     */
    boolean exposeProxy() default false;

}

進一步看一下

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * Register, escalate, and configure the AspectJ auto proxy creator based on the value
     * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
     * {@code @Configuration} class.
     */
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 這是重點------這是重點------這是重點------
    // 這里就是注入AnnotationAwareAspectJAutoProxyCreator對象入口
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

AnnotationAwareAspectJAutoProxyCreator類的UML圖
image-20210701142934998.png

可以看到细疚,AnnotationAwareAspectJAutoProxyCreator這個類是實現(xiàn)了InstantiationAwareBeanPostProcessor蔗彤、SamrtInstantiationAwareBeanPostProcessor、BeanPostProcessor接口的惠昔,那么說明這個類是可以在Bean實例創(chuàng)建過程中幕与,對Bean創(chuàng)建過程進行攔截和增強的。

同樣的镇防,AspectJAwareAdvisorAutoProxyCreator這個類其實就是基于XML配置來對Bean進行攔截和增強的啦鸣。

1.在Bean實例化之前:

@Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        Object cacheKey = getCacheKey(beanClass, beanName);

        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }
    // 這是重點----這是重點-----這是重點
    // 從這里可以看出,這又是一種創(chuàng)建Bean實例的方式
    // 通過自定義的TargetSource來創(chuàng)建Bean實例来氧。屬于非常規(guī)用法诫给。
        // Create proxy here if we have a custom TargetSource.
        // Suppresses unnecessary default instantiation of the target bean:
        // The TargetSource will handle target instances in a custom fashion.
        TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
      // 收集所有的Advisor后,通過MethodInterceptor創(chuàng)建代理啦扬。具體的實現(xiàn)可以是JDK或者CGLIB
            Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

        return null;
    }

在此基礎上中狂,我們可以自定義一個BeanPostProcessor,針對AnnotationAwareAspectJAutoProxyCreator進行攔截扑毡,在AnnotationAwareAspectJAutoProxyCreator初始化生命周期中胃榕,給它創(chuàng)建一個TargetSource對象設置進去,這樣就可以促使getCustomTargetSource()方法生效了瞄摊。

2.另一個就是在Bean實例化后勋又,把Bean對象放進第三級緩存中

// AbstractAutowireCapableBeanFactory類中587行
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
          // ********這是關(guān)鍵點********這是關(guān)鍵點********這是關(guān)鍵點********這是關(guān)鍵點********
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

// AbstractAutoProxyCreator類中237行,AbstractAutoProxyCreator是SmartInstantiationAwareBeanPostProcessor子類换帜,是實現(xiàn)AOP的入口
@Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
    // ********這是關(guān)鍵點********這是關(guān)鍵點********這是關(guān)鍵點********這是關(guān)鍵點********
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && 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;
        }

        // 獲取所有的Advisor實例楔壤,并篩選出匹配的Advisor實例
    // 獲取Advisor的方式也是通過getBean()的方式,根據(jù)指定目標為Advisor.class查找
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
      // 根據(jù)篩選出來的Advisor實例惯驼,創(chuàng)建代理對象
      // 如何創(chuàng)建的代理蹲嚣,其實就是使用了JDK動態(tài)代理或者CGLIB動態(tài)代理實現(xiàn)
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
      // 返回增強后的Bean對象
            return proxy;
        }

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

3.Bean初始化后

// AbstractAutoProxyCreator類中295行
@Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
        // ********這是關(guān)鍵點********同上********同上********這是關(guān)鍵點********
        // 和上面的wrapIfNecessary()一樣
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

根據(jù)AbstractAutoProxyCreator來看,AOP切入的點有三個地方:

1.postProcessBeforeInstantiation():就是在Bean還沒有創(chuàng)建出來之前祟牲,這個點是留給開發(fā)者自己擴展的隙畜。針對自定義TargetSource,檢查是否有匹配的Advisor说贝,如果有的話禾蚕,那么就創(chuàng)建對應的代理對象。

2.postProcessAfterInitialization():這個就是Bean已經(jīng)完成了初始化了狂丝,這個時候的Bean已經(jīng)準備發(fā)布了换淆,那么針對這個Bean對象,檢查是否有匹配的Advisor几颜,有的話倍试,就創(chuàng)建對應的代理對象。

3.getEarlyBeanReference():這個就是Bean剛剛創(chuàng)建出來蛋哭,還沒有設置屬性值县习,還不能發(fā)布,但是為了解決循環(huán)依賴的問題谆趾,需要先把這個Bean的引用發(fā)布出去躁愿。那么針對這個Bean就要檢查是否有匹配的Advisor,有的話沪蓬,創(chuàng)建代理對象彤钟。

在這三個擴展點,分別會做以下幾件事情:

1.findCandidateAdvisors()方法獲取所有的Advisor實例跷叉,并存放到一個數(shù)組里面逸雹;原理還是getBean()方式查找Advisor.class所有實例

2.findAdvisorsThatCanApply()方法從數(shù)組中篩選出攔截這個Bean的Advisor,有攔截class的云挟,也有method梆砸;主要區(qū)分出IntroductionAdvisor、PointcutAdvisor园欣;

  • 2.1:IntroductionAdvisor只能攔截class帖世,PointcutAdvisor可以攔截method和class。

3.針對篩選出來的Advisor排序

4.根據(jù)篩選出來的Advisor對象沸枯,通過ProxyFactory.getProxy()創(chuàng)建代理日矫。

另外,再介紹兩個手動編程創(chuàng)建代理對象的類:

  • AspectJProxyFactory
    • 這個類包含兩個功能:創(chuàng)建代理的功能辉饱;處理Advisor搬男、Advice的功能
  • ProxyFactory
    • 這個類只能創(chuàng)建代理,Advisor彭沼、Advice需要自己手動創(chuàng)建

以上兩個類的區(qū)別在于缔逛,AspectJProxyFactory這個類里面有一個ReflectiveAspectJAdvisorFactory對象屬性專門用來處理并生成Advisor、Advice姓惑。

共同點就是都能創(chuàng)建代理對象褐奴,主要是因為這兩個類都繼承自ProxyCreatorSupport。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末于毙,一起剝皮案震驚了整個濱河市敦冬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唯沮,老刑警劉巖脖旱,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堪遂,死亡現(xiàn)場離奇詭異,居然都是意外死亡萌庆,警方通過查閱死者的電腦和手機溶褪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來践险,“玉大人猿妈,你說我怎么就攤上這事∥〕妫” “怎么了彭则?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長占遥。 經(jīng)常有香客問我俯抖,道長,這世上最難降的妖魔是什么筷频? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任蚌成,我火速辦了婚禮,結(jié)果婚禮上凛捏,老公的妹妹穿的比我還像新娘担忧。我一直安慰自己,他們只是感情好坯癣,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布瓶盛。 她就那樣靜靜地躺著,像睡著了一般示罗。 火紅的嫁衣襯著肌膚如雪惩猫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天蚜点,我揣著相機與錄音轧房,去河邊找鬼。 笑死绍绘,一個胖子當著我的面吹牛奶镶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陪拘,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼厂镇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了左刽?” 一聲冷哼從身側(cè)響起捺信,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎欠痴,沒想到半個月后迄靠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秒咨,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年梨水,在試婚紗的時候發(fā)現(xiàn)自己被綠了拭荤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡疫诽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旦委,到底是詐尸還是另有隱情奇徒,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布缨硝,位于F島的核電站摩钙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏查辩。R本人自食惡果不足惜胖笛,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宜岛。 院中可真熱鬧长踊,春花似錦、人聲如沸萍倡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽列敲。三九已至阱佛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戴而,已是汗流浹背凑术。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留所意,地道東北人淮逊。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像扁眯,于是被迫代替她去往敵國和親壮莹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容