AutowireCapableBeanFactory探密(1)——為第三方框架賦能

起因

群里有朋友拋出了個問題琅锻,問為什么Spring Cache注解未生效帮掉,示例代碼如下:

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class DemoApplicationTests {
    @Test
    public void contextLoads() {
        testGetFromDB(1);
        testGetFromDB(1);
        testGetFromDB(1);
    }

    @Cacheable("foo")
    public String testGetFromDB(Integer i) {
        log.info("testGetFromDB...");
        return "retVal" + i;
    }
}

我說咙俩,你把@Cacheable注解的方法搬到別的類耿戚,然后再試試,結(jié)果,好了

大家如果有過類似經(jīng)驗膜蛔,大概能一眼看出問題所在:Spring AOP方法內(nèi)部調(diào)用晓锻,切面邏輯是不生效的(動態(tài)代理)。解決方案有兩個飞几,一是AopContext#currentProxy砚哆,二是在當(dāng)前類中注入自己,再用該對象進(jìn)行方法內(nèi)部調(diào)用

但實際上屑墨,這里隱藏了一個問題躁锁,即便通過上述的兩種方式,在本案例中卵史,@Cacheable仍然不會生效战转。第一種方式會因獲取到的proxy對象為空而拋出IllegalStateException異常,第二種方式會因獲取不到DemoApplicationTestsBean而拋出NoSuchBeanDefinitionException異常

public static Object currentProxy() throws IllegalStateException {
    Object proxy = currentProxy.get();
    if (proxy == null) {
        throw new IllegalStateException(
                "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
    }
    return proxy;
}
NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.DemoApplicationTests' available

這就很詭異了以躯,為啥會獲取不到槐秧?難道IOC容器里沒有DemoApplicationTests對象?可是忧设,如果沒有這個對象刁标,那平時寫單測的時候,那些@Resouce址晕、@Autowired膀懈、@Value注解(Spring稱為:annotation-driven injection)又是如何生效的,Bean是如何注入的呢

實際上谨垃,IOC容器里還真沒有DemoApplicationTests對象启搂,也就是說,Spring在IOC容器里壓根找不到該Bean刘陶,也就不難理解方式一胳赌、二都拋了異常。

在一般的認(rèn)知中匙隔,如果某個Bean想使用Spring的功能疑苫,需要讓Bean注冊到Spring IOC容器中,被Spring管理牡直。那么問題就來了缀匕,既然Spring 并沒有管理DemoApplicationTestsBean,那annotation-driven injection是如何生效的呢碰逸?

解決方案

AutowireCapableBeanFactory
  1. BeanFactory的直接子接口乡小,擁有自動裝配的能力
  2. 一般不會在普通應(yīng)用中直接使用AutowireCapableBeanFactory,因此在設(shè)計上ApplicationContext們并沒有直接實現(xiàn)該接口(畫外音:Spring的設(shè)計者們不希望用戶在應(yīng)用代碼中直接使用AutowireCapableBeanFactory)饵史。盡管如此满钟,卻提供了該問該接口的能力:通過ApplicationContext#getAutowireCapableBeanFactory方法可拿到其實例對象
  3. 它真正的作用在于整合其它框架:能讓Spring管理的Bean去裝配和填充那些不被Spring托管的Bean(wire and populate existing bean instances that Spring does not control the lifecycle of)[重要]

原理

源碼基于Spring 5.1.11.RELEASE

當(dāng)在Junit測試類使用spring-test\spring-boot-test注解(如:@SpringBootTest)胜榔,并啟動測試方法時,Junit框架會準(zhǔn)備相關(guān)的測試資源湃番,并且加載Spring環(huán)境夭织,之后對測試類實例使用Spring進(jìn)行裝配,以保證依賴的服務(wù)處于可用狀態(tài)吠撮。測試方法啟動過程會經(jīng)歷如下方法:

// org.springframework.test.context.support.DependencyInjectionTestExecutionListener

protected void injectDependencies(TestContext testContext) throws Exception {
    Object bean = testContext.getTestInstance(); // 測試類實例
    Class<?> clazz = testContext.getTestClass(); // 測試類class(DemoApplicationTests)
    AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext().getAutowireCapableBeanFactory();
    // 1. 使用AutowireCapableBeanFactory去裝配不受Spring管理的bean
    beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
    // 2. 進(jìn)行初始化
    beanFactory.initializeBean(bean, clazz.getName() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX);
    
    testContext.removeAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE);
}

從上述源碼中可以看到尊惰,Bean(DemoApplicationTests)是從testContext中獲取,而并沒有被Spring管理泥兰,然后借助AutowireCapableBeanFactory的能力對Bean進(jìn)行裝配與初始化弄屡,讓Spring為非Spring托管的Bean賦能

其中,對于@Resouce鞋诗、@Autowired注入的能力膀捷,是通過beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);來實現(xiàn)的,代碼如下:

public void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException {
    if (autowireMode == AUTOWIRE_CONSTRUCTOR) {
        throw new IllegalArgumentException("AUTOWIRE_CONSTRUCTOR not supported for existing bean instance");
    }
    // Use non-singleton bean definition, to avoid registering bean as dependent bean.
    RootBeanDefinition bd =
            new RootBeanDefinition(ClassUtils.getUserClass(existingBean), autowireMode, dependencyCheck);
    bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
    BeanWrapper bw = new BeanWrapperImpl(existingBean);
    initBeanWrapper(bw);
    // 填充屬性
    populateBean(bd.getBeanClass().getName(), bd, bw);
}

接著看populateBean方法削彬,該方法作用是為給定的bean填充屬性全庸,相信大家已經(jīng)很熟悉這段代碼了,篇幅原因融痛,把非關(guān)鍵代碼省略

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    // ...(省略)
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            // @Resouce壶笼、@Autowired 注解起作用的地方
            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
                if (filteredPds == null) {
                    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                }
                pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    return;
                }
            }
            pvs = pvsToUse;
        }
    }
    // ...(省略)
}

@Resouce@Autowired注解之所以能生效酌心,是因為最終執(zhí)行了PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);這一行代碼拌消。有兩個InstantiationAwareBeanPostProcessor分別是CommonAnnotationBeanPostProcessor挑豌、AutowiredAnnotationBeanPostProcessor安券,分別用于處理@Resouce@Autowired氓英,達(dá)到屬性填充的目的

接下來看看AutowireCapableBeanFactory接口侯勉,一共有6個屬性,15個方法

public interface AutowireCapableBeanFactory extends BeanFactory {

// ---------------------- 屬性 ----------------------
    // 裝配模式
    int AUTOWIRE_NO = 0;

    int AUTOWIRE_BY_NAME = 1;

    int AUTOWIRE_BY_TYPE = 2;

    int AUTOWIRE_CONSTRUCTOR = 3;

    // @deprecated as of Spring 3.0
    int AUTOWIRE_AUTODETECT = 4;

    String ORIGINAL_INSTANCE_SUFFIX = ".ORIGINAL";

// ---------------------- 方法 ----------------------

    <T> T createBean(Class<T> beanClass) throws BeansException;

    void autowireBean(Object existingBean) throws BeansException;

    Object configureBean(Object existingBean, String beanName) throws BeansException;

    Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;

    Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;

    void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)
            throws BeansException;

    void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException;

    Object initializeBean(Object existingBean, String beanName) throws BeansException;

    Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
            throws BeansException;

    Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException;

    void destroyBean(Object existingBean);

    <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType) throws BeansException;

    Object resolveBeanByName(String name, DependencyDescriptor descriptor) throws BeansException;

    Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) throws BeansException;

    Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;

}

5個AUTOWIRE_*指的是裝配模式铝阐,接口方法中的autowireMode參數(shù)取值就從這5個里邊選擇

  • int AUTOWIRE_NO = 0;不裝配
  • int AUTOWIRE_BY_NAME = 1;根據(jù)名稱裝配
  • int AUTOWIRE_BY_TYPE = 2;根據(jù)類型裝配
  • int AUTOWIRE_CONSTRUCTOR = 3;根據(jù)構(gòu)造器裝配
  • int AUTOWIRE_AUTODETECT = 4;從Spring 3.0就過期了址貌,不作介紹

還有1個屬性String ORIGINAL_INSTANCE_SUFFIX = ".ORIGINAL";,該屬性是一種約定俗成的用法:以類全限定名+.ORIGINAL 作為Bean Name徘键,用于告訴Spring练对,在初始化的時候,需要返回原始給定實例吹害,而別返回代理對象

上面的方法看似挺多螟凭,經(jīng)過分類之后,也能比較好理解與記憶它呀。其實從方法的命名上也能看出來各自的作用螺男,這也是Spring框架優(yōu)秀讓人著迷的地方之一:方法命名在很多時候能讓人見名知義棒厘,足夠抽象而又不失準(zhǔn)確的概括。按照接口提供能力的粒度下隧,大體上分為三類:

  1. 粗粒度創(chuàng)建奢人、裝配Bean的方法
<T> T createBean(Class<T> beanClass) throws BeansException;
void autowireBean(Object existingBean) throws BeansException;
Object configureBean(Object existingBean, String beanName) throws BeansException;
  1. 細(xì)粒度控制Bean生命周期的方法(創(chuàng)建、裝配Bean的過程涉及Bean的生命周期)淆院。細(xì)粒度體現(xiàn)在兩個方面何乎,一是提供了可選的裝配模式(autowireMode),二是把裝配Bean的過程細(xì)化為 屬性填充(populateBean)初始化(initializeBean) 兩個階段土辩。此外宪赶,還提供了BeanPostProcessors的前后置處理邏輯回調(diào),以及銷毀Bean的邏輯回調(diào)脯燃。
Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException;
void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException;
Object initializeBean(Object existingBean, String beanName) throws BeansException;
Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException;
Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException;
void destroyBean(Object existingBean);
  1. 解析注入點的代理方法搂妻,這些方法是輔助類方法,一般不需要直接使用
<T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType) throws BeansException;
Object resolveBeanByName(String name, DependencyDescriptor descriptor) throws BeansException;
Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) throws BeansException;
Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;

上面是官方的分法辕棚,下面將按照接口的功能進(jìn)行劃分

  1. 創(chuàng)建Bean
    • createBean(Class<T> beanClass): 用給定的class創(chuàng)建一個Bean實例欲主,完整經(jīng)歷一個Bean創(chuàng)建過程的生命周期節(jié)點回調(diào),但不執(zhí)行傳統(tǒng)的autowiring
      • 提供autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck)所有能力
      • 提供initializeBean(Object existingBean, String beanName)所有能力
    • createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck): 與上邊類似逝嚎,主要區(qū)別是可以指定autowireMode扁瓢,即可能會執(zhí)行傳統(tǒng)的autowiring
  2. 屬性裝配
    • autowireBean(Object existingBean): 主要用于(再次)填充指定Bean被注解的元素或方法(如@Resource @Autowired),不執(zhí)行傳統(tǒng)的autowiring
      • 執(zhí)行InstantiationAwareBeanPostProcessor的后置處理邏輯补君,因為這是在populateBean()中執(zhí)行的
      • 不執(zhí)行傳統(tǒng)的autowiring
      • 不執(zhí)行initializeBean方法引几,因此也就不執(zhí)行標(biāo)準(zhǔn)的BeanPostProcessor
    • autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck): 與上邊類似,主要區(qū)別是可以指定autowireMode挽铁,即可能會執(zhí)行傳統(tǒng)的autowiring
    • autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck): 與上邊類似伟桅,主要區(qū)別是該方法首個入?yún)⑹荂lass挪拟,框架會幫我們實例化声离,而上邊是已實例化的對象
  3. 初始化
    • initializeBean(Object existingBean, String beanName): 初始化給定的Bean
      • 執(zhí)行invokeAwareMethods方法(xxxAware,如BeanNameAware)
      • 執(zhí)行BeanPostProcessor前置處理邏輯
      • 執(zhí)行invokeInitMethods進(jìn)行初始化缀旁,如InitializingBeanorg#afterPropertiesSet等
      • 執(zhí)行BeanPostProcessor后置處理邏輯
  4. 配置Bean
    • configureBean(Object existingBean, String beanName): 作為initializeBean的超集存在更扁,功能跟createBean(Class<T> beanClass)類似盖腕,但要求beanName對應(yīng)的BeanDefinition存在,否則會拋出NoSuchBeanDefinitionException異常浓镜,使用場景很少
  5. 銷毀Bean
    • destroyBean(Object existingBean): 銷毀Bean前溃列,執(zhí)行DestructionAwareBeanPostProcessorDisposableBean等的銷毀邏輯,用于資源回收
  6. 對Bean初始化前后應(yīng)用邏輯處理器
    • applyBeanPostProcessorsBeforeInitialization: 執(zhí)行BeanPostProcessor前置處理邏輯
    • applyBeanPostProcessorsAfterInitialization: 執(zhí)行BeanPostProcessor后置處理邏輯
    • applyBeanPropertyValues: 真正執(zhí)行Bean屬性值的填充
  7. 解析方法膛薛,輔助類方法
    • resolveNamedBean(Class<T> requiredType): 根據(jù)傳入的類型听隐,從Spring容器(包括父子容器)中查找出指定類型下唯一的Bean,并將beanNamebeanInstance包裝成NamedBeanHolder對象返回相叁。如果該類型下有不止一個對象(非唯一)遵绰,返回null辽幌。該方法其實是BeanFactory#getBean(java.lang.Class<T>)方法的變種,二者底層實現(xiàn)是一樣的
    • resolveBeanByName(String name, DependencyDescriptor descriptor): 根據(jù)name跟descriptor解析出一個Bean實例椿访,該方法是BeanFactory#getBean(java.lang.String, java.lang.Class<T>)方法的變種
    • resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName): 直接調(diào)用下邊的方法
    • resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter): 解析Bean依賴乌企,非常重要的方法

AbstractAutowireCapableBeanFactoryAutowireCapableBeanFactory的一個抽象實現(xiàn)類,該類的源碼如果不往更底層看成玫,其實還是比較簡單(簡潔)的加酵,大家以前學(xué)習(xí)Spring Bean生命周期的時候應(yīng)該見到過,因此也就不貼源碼了哭当,感興趣的同學(xué)可以自行翻看一下

除了Junit猪腕,還有Quartz定時任務(wù)框架,與Spring集成的時候钦勘,也會用上AbstractAutowireCapableBeanFactory陋葡。原因是定時任務(wù)Job一般需要借助Spring 管理的Service來進(jìn)行業(yè)務(wù)邏輯的處理,但Job本身并沒有交給Spring管理彻采,而是通過反射生成實例腐缤,因此想要借用@Resouce等注解,就得讓AbstractAutowireCapableBeanFactory為Job Bean 做自動裝配肛响。具體分析可參看此處

總結(jié)

Spring 提供了一種機(jī)制岭粤,能夠為第三方框架賦能,讓Spring管理的Bean去裝配和填充那些不被Spring托管的Bean特笋,這種機(jī)制叫AutowireCapableBeanFactory剃浇。目前了解到的有兩款著名的開源框架JunitQuartz借用了這種機(jī)制為自己賦能,達(dá)到更好地與Spring契合協(xié)作的目的猎物,也從他們的整合姿勢中學(xué)習(xí)到虎囚,當(dāng)我們需要設(shè)計一個框架,而且需要擁抱Spring生態(tài)霸奕,為框架使用者提供更便利的無縫整合溜宽,需以微內(nèi)核 + 插件機(jī)制思想,由內(nèi)核層想辦法拿到AutowireCapableBeanFactory质帅,并在構(gòu)造插件的時候,借助AutowireCapableBeanFactory去為插件賦能留攒,做到無限擴(kuò)展的可能性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末煤惩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子炼邀,更是在濱河造成了極大的恐慌魄揉,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拭宁,死亡現(xiàn)場離奇詭異洛退,居然都是意外死亡瓣俯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門兵怯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彩匕,“玉大人,你說我怎么就攤上這事媒区⊥找牵” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵袜漩,是天一觀的道長绪爸。 經(jīng)常有香客問我,道長宙攻,這世上最難降的妖魔是什么奠货? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮座掘,結(jié)果婚禮上仇味,老公的妹妹穿的比我還像新娘。我一直安慰自己雹顺,他們只是感情好丹墨,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嬉愧,像睡著了一般贩挣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上没酣,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天王财,我揣著相機(jī)與錄音,去河邊找鬼裕便。 笑死绒净,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偿衰。 我是一名探鬼主播挂疆,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼下翎!你這毒婦竟也來了缤言?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤视事,失蹤者是張志新(化名)和其女友劉穎胆萧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俐东,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡跌穗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年订晌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚌吸。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡锈拨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出套利,到底是詐尸還是另有隱情推励,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布肉迫,位于F島的核電站验辞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏喊衫。R本人自食惡果不足惜跌造,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望族购。 院中可真熱鬧壳贪,春花似錦、人聲如沸寝杖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瑟幕。三九已至磕蒲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間只盹,已是汗流浹背辣往。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留殖卑,地道東北人站削。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像孵稽,于是被迫代替她去往敵國和親许起。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

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