Spring中眼見爲虛的 @Configuration 配置類

有道無術,術尚可求也!有術無道,止於術!

1、前言

在這裏我不得不感慨Spring的代碼的完善與優秀,從以前看源碼迷迷糊糊到如今基本瞭解Spring的部分源碼後,越來越發現Spring開發者的思慮之周全!java

以前說過學習源碼的目的在哪?正如我特別喜歡的一句話,有道無術,術尚可求也!有術無道,止於術!,對於Spring的瞭解僅僅侷限於使用遠遠不夠,Spring做爲一個國內絕大多數java開發者使用的一個項目管理框架,他是一個生態,什麼是生態?好比如今的SpringBootSpringCloud,他們是什麼?是Spring生態中的一個組成部分!他們利用Spring生態中提供的各類擴展點,一步一步的封裝,成就瞭如今Spring快速啓動自動配置等亮眼的功能!做爲Spring的使用者,咱們理應瞭解Spring的實現和各類擴展點,從而可以真正的深刻Spring生態!深刻了,再去研究生態中的組成部分如:SpringBoot之流的框架,也就水到渠成了!web

2、開篇一問

相信大部分開發者對於Spring的使用都是水到渠成的!那麼下面一段代碼你們必定很熟悉!面試

/**
 * 全局配置類
 *
 * @author huangfu
 */

@Configuration
public class ExpandRunConfig {
 @Bean
 public TestService testService() {
  return new TestServiceImpl();
 }

 @Bean
 public UserService userService() {
  testService();
  return new UserServiceImpl();
    }
}

能夠很清楚的看到,這裏交給Spring管理了兩個類TestService,UserService,可是在userService()裏面又引用了testService()! 那麼問題來了,你以爲TestService會被實例化幾回?spring

相信有很多同窗,張口就說一次,對,沒錯,可是爲何呢?我當時對這裏的問題深深的感到自我懷疑!甚至一度懷疑本身的java基礎,明明這裏調用了另一個方法,可是爲何沒有進行兩次實例化呢?微信

我問了不少同事、朋友,他們只知道這樣寫是沒有問題的!可是具體緣由不知道!爲何呢?咱們帶着這個問題往下看!app

3、你看到的配置類是真的配置類嗎?

咱們從bean容器裏面把這個配置類取出來,看一下有什麼不同!框架

public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ExpandRunConfig.class);
    ExpandRunConfig bean = ac.getBean(ExpandRunConfig.class);
    System.out.println(bean);

}

咱們debug看一下,咱們取出來了個什麼玩意!編輯器

被代理的Spring配置類

果真,他不是他了,他被(玷污)代理了,並且使用的代理是cglib,那麼這裏就能夠猜想一個問題,在Bean方法中調用另一個Bean方法,他必定是經過代理來作的,從而完成了屢次調用只實例化一次的功能!ide

到這裏,解決了,原來是這樣!那麼如今有兩個疑問:post

  1. 何時給配置類加的代理?
  2. 代理邏輯裏面是怎麼完成屢次調用返回同一個實例的功能的?

下面咱們就帶着兩個疑問,去追一下Spring源碼,看看究竟是如何進行的!

4、代理圖示

cglib代理配置類的流程圖

這張圖我放出來,若是你沒有了解過的話,必定是很迷惑,不要緊,後面會用源碼解釋,並且源碼看完以後,咱們會大概手寫一個,幫助你理解!

5、源碼詳解

不妨猜一下,看過我之前的文章的讀者都應該瞭解!Spring建立bean實例的時候,所須要的信息是在beanDefinitionMap裏面存放的,那麼在初始化的時候解析bean的bd的時候,必定是替換了配置類bd裏面的類對象,纔會使後面實例化config的時候變成了一個代理對象,因此咱們的入口應該在這裏:

invokerBeanFactory入口方法

那麼這裏面的代碼是在哪加強的呢?

/**
  * 準備配置類以在運行時爲Bean請求提供服務
  * 經過用CGLIB加強的子類替換它們。
  */

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    ..................忽略對應的邏輯................
    //字節碼加強配置類  貌似用的cglib
    enhanceConfigurationClasses(beanFactory);
    ..................忽略對應的邏輯................
}

調用配置類的加強邏輯  enhanceConfigurationClasses

/**
 * 對BeanFactory進行後處理以搜索配置類BeanDefinitions; 而後,任何候選人都將經過{@link ConfigurationClassEnhancer}.
 * 候選狀態由BeanDefinition屬性元數據肯定。
 * @see ConfigurationClassEnhancer
 */

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    // 最終須要作加強的Bean定義們
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
        //什麼是Full類,簡單來講就是加了 @Configuration 的配置類
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
           .....忽略日誌打印......
            //// 若是是Full模式,纔會放進來
            configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
        }
    }
    if (configBeanDefs.isEmpty()) {
        // 沒有什麼可加強的->當即返回
        return;
    }
    //配置類加強器
    // ConfigurationClassEnhancer就是對配置類作加強操做的核心類
    //初始化會初始化兩個chlib攔截類  BeanFactoryAwareMethodInterceptor 和  BeanMethodInterceptor
    //這個是重點  這個類裏面的方法會產生最終的代理類
    //這個方法裏面有個
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    //對每一個Full模式的配置類,一個個作enhance()加強處理
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
        AbstractBeanDefinition beanDef = entry.getValue();
        // 若是@Configuration類被代理,請始終代理目標類
        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
        try {
            // 設置用戶指定的bean類的加強子類
            //CGLIB是給父類生成子類對象的方式實現代理,因此這裏指定「父類」類型
            Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
            if (configClass != null) {
                //作加強處理,返回enhancedClass就是一個加強過的子類
                //這個是重點,這個會構建一個cglib的加強器,最終返回被代理完成的類對象!
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
                //不相等,證實代理成功,那就把實際類型設置進去
                if (configClass != enhancedClass) {
                    ..... 忽略日誌打印 ....
                    //這樣後面實例化配置類的實例時,實際實例化的就是加強子類嘍
                    //這裏就是替換 config類的beanClass對象的!
                    beanDef.setBeanClass(enhancedClass);
                }
            }
        }
        catch (Throwable ex) {
            。。。。。忽略異常處理。。。。。。。
        }
    }
}

這個類相當重要,總共作了這樣幾件事:

  1. 篩選配置類,只有加了 @Configuration的配置類纔會被加強!
  2. 使用 enhancer.enhance構建一個加強器,返回加強後的代理類對象!
  3. 替換配置類原始的beanClass,爲代理後的class!

那麼,咱們最關心的是如何實現的,確定要看enhancer.enhance裏面的邏輯~

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
  // 若是已經實現了該接口,證實已經被代理過了,直接返回
  if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
   。。。。忽略日誌打印。。。。
   return configClass;
  }
  //沒被代理過。就先調用newEnhancer()方法建立一個加強器Enhancer
  //而後在使用這個加強器,生成代理類字節碼Class對象
  //建立一個新的CGLIB Enhancer實例,而且作好相應配置
        //createClass是設置一組回調(也就是cglib的方法攔截器)
  Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
  if (logger.isTraceEnabled()) {
   。。。。忽略日誌打印。。。。
  }
  return enhancedClass;
 }

這是一個過分方法,真正去構建一個代理加強器的是newEnhancer方法,咱們彷佛接近了咱們要的答案!

/**
  * 建立一個新的CGLIB {@link Enhancer}實例。
  */

private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    // 目標類型:會以這個做爲父類型來生成字節碼子類
    enhancer.setSuperclass(configSuperClass);
    //代理類實現EnhancedConfiguration接口,這個接口繼承了BeanFactoryAware接口
    //這一步頗有必要,使得配置類強制實現 EnhancedConfiguration即BeanFactoryAware 這樣就能夠輕鬆的獲取到beanFactory
    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
    // 設置生成的代理類不實現org.springframework.cglib.proxy.Factory接口
    enhancer.setUseFactory(false);
    //設置代理類名稱的生成策略:Spring定義的一個生成策略 你名稱中會有「BySpringCGLIB」字樣
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    //設置攔截器/過濾器  過濾器裏面有一組回調類,也就是真正的方法攔截實例
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
}

若是你熟悉cglib的話,確定對這幾行代碼熟悉無比,主要作了這樣幾件事!

  1. 設置須要代理的類
  2. 設置生成的代理類須要實現的接口,這裏設置實現了 EnhancedConfiguration,注意這個是一個很騷的操做,他是可以保證最終類可以從beanFactory返回的一個重要邏輯,爲何?由於 EnhancedConfigurationBeanFactoryAware的子類,Spring會回調他,給他設置一個 beanFactory ,若是你看不懂不妨先把和這個記下來,等看完在回來仔細品味一下!
  3. 設置過濾器,過濾器裏面實際上是一組回調方法,這個回調方法是最終方法被攔截後執行的真正邏輯,咱們一會要分析的也是過濾器裏面這一組回調實例!
  4. 返回最終的加強器!

剛剛也說了,咱們須要重點關注的是這一組攔截方法,咱們進入到攔截器裏面,找到對應的回調實例!

CALLBACK_FILTER:常量對應的是一個過濾器,咱們看它如何實現的:

private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

那麼此時 CALLBACKS 就是我麼要找的回調方法,點進去能夠看到:

// 要使用的回調。請注意,這些回調必須是無狀態的。
private static final Callback[] CALLBACKS = new Callback[] {
    //這個是真正可以Bean方法屢次調用返回的是一個bean實例的實際攔截方法,這個攔截器就是徹底可以說明,爲何屢次調用只返回
    //一個實例的問題
    new BeanMethodInterceptor(),
    //攔截 BeanFactoryAware 爲裏面的 setBeanFactory 賦值
    //剛剛也說了,加強類會最終實現 BeanFactoryAware 接口,這裏就是攔截他的回調方法 setBeanFactory方法,獲取bean工廠!
    new BeanFactoryAwareMethodInterceptor(),
    //這個說實話  真魔幻  我本身實現cglib的時候一直在報錯  報一個本身拋出的異常,異常緣由是沒有處理object裏面的eques等
    //方法,這個就是爲了處理那些沒有被攔截的方法的實例  這個些方法直接放行
    //這個實例裏面沒有實現任何的東西,空的,表明着不處理!
    NoOp.INSTANCE
};

具體裏面每個攔截器到底是幹嗎的,註釋說的很明白,咱們從第二個提及!爲何不從第一個呢?第一個比較麻煩,咱們由淺入深,逐步的說!

BeanFactoryAwareMethodInterceptor

/**
  * 攔截對任何{@link BeanFactoryAware#setBeanFactory(BeanFactory)}的調用 {@code @Configuration}類實例,用於記錄{@link BeanFactory}。
  * @see EnhancedConfiguration
  */

private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptorConditionalCallback {

    @Override
    @Nullable
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //找到本類(代理類)里名爲`$$beanFactory`的字段
        Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
        //若沒找到直接報錯。若找到了此字段,就給此字段賦值
        Assert.state(field != null"Unable to find generated BeanFactory field");
        field.set(obj, args[0]);

        // 實際的(非CGLIB)超類是否實現BeanFactoryAware?
        // 若是是這樣,請調用其setBeanFactory()方法。若是沒有,請退出。
        //若是用戶類(也就是你本身定義的類)本身實現了該接口,那麼別擔憂,也會給你賦值上
        if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
            return proxy.invokeSuper(obj, args);
        }
        return null;
    }

    /**
  * 執行到setBeanFactory(xxx)方法時匹配成功
  * @param candidateMethod 當前執行的方法
  * @return
  */

    @Override
    public boolean isMatch(Method candidateMethod) {
        //判斷方法是否是 `setBeanFactory` 方法 
        return isSetBeanFactory(candidateMethod);
    }
    
    .........忽略沒必要要邏輯.........
}

不知道你注意沒有,在最終生成的代理配置類裏面有一個 $$beanFactory屬性,這個屬性就是在這裏被賦值的!再把圖片放出來,看最後一個屬性!

被代理的Spring配置類

這個攔截器的主要做用:

  1. 攔截 setBeanFactory方法,爲 $$beanFactory賦值!

好了,這個攔截器介紹完了,功能你們也記住了,那麼,咱們分析下一個攔截器,這個是重點!

BeanMethodInterceptor

/**
 * 加強{@link Bean @Bean}方法以檢查提供的BeanFactory中的 這個bean對象的存在。
 * @throws Throwable 做爲全部在調用時可能引起的異常的統籌 代理方法的超級實現,即實際的{@code @Bean}方法
 * 當該方法通過匹配成功後 會進入到這個攔截方法  這個是解決bean方法只被建立一次的重要邏輯
 */

@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
                        MethodProxy cglibMethodProxy)
 throws Throwable 
{
    //經過反射,獲取到Bean工廠。也就是 $$beanFactory 這個屬性的值
    //也就是上一個攔截器被注入的值
    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    //拿到Bean的名稱
    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

    // 肯定此bean是否爲做用域代理
    //方法頭上是否標註有@Scoped註解
    if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
        }
    }
    。。。。。。忽略與本題無關的代碼。。。。。。。。。。
        
    // 檢查給定的方法是否與當前調用的容器相對應工廠方法。
    // 比較方法名稱和參數列表來肯定是不是同一個方法
    // 怎麼理解這句話,參照下面詳解吧
    //在整個方法裏面,我認爲這個判斷是核心,爲何說他是核心,由於只有這個判斷返回的是false的時候他纔會真正的走加強的邏輯
    //何時會是false呢?
    //首先  spring會獲取到當前使用的方法   其次會獲取當前調用的方法,當兩個方法不一致的時候會返回false
    //什麼狀況下胡不一致呢?
    //當在bean方法裏面調用了另外一個方法,此時當前方法和調用方法不一致,致使返回課false而後去執行的加強邏輯
    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
        // 這是個小細節:若你@Bean返回的是BeanFactoryPostProcessor類型
        // 請你使用static靜態方法,不然會打印這句日誌的~~~~
        // 由於若是是非靜態方法,部分後置處理失效處理不到你,可能對你程序有影像
        // 固然也可能沒影響,因此官方也只是建議而已~~~
        if (logger.isInfoEnabled() &&
            BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
            ...... 忽略日誌打印......
        }
        // 這表示:當前方法,就是這個被攔截的方法,那就沒啥好說的
        // 至關於在代理代理類裏執行了super(xxx);
        // 可是,可是,可是,此時的this依舊是代理類
        //這個事實上上調用的是自己的方法  最終會再次被調用到下面的 resolveBeanReference 方法
        //這裏的設計很奇妙  爲何這麼說呢?
        //瞭解這個方法首先要對cglib有一個基礎的認識 爲何這麼說唄?
        //首先要明白 cglib是基於子類集成的方式去加強的目標方法的
        //因此在不進行加強的時候就能夠以很輕鬆的調用父類的原始方法去執行實現
        //當前調用的方法和調用的方法是一個方法的時候  就直接調用cglib父類  也就是原始類的建立方法直接建立
        //當不同的時候  會進入到下面的方法  直接由beanFactory返回  精妙!!
        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
    }
    //方法裏調用的實例化方法會交給這裏來執行
    //這一步的執行是真正的執行方式,當發現該方法須要代理的時候不調用父類的原始方法
    //而是調用我須要代理的邏輯去返回一個對象,從而完成對對象的代理
    return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

乍一看,是否是好多,沒事咱們一點一點分析:

  1. 首先我麼看那個判斷 if (isCurrentlyInvokedFactoryMethod(beanMethod))這個判斷是很重要的!他就是從 ThreadLocal裏面取出本次調用的工廠方法,前面提到過不少次工廠方法,什麼是工廠方法?就是你寫的那個@Bean對應的方法,咱們就叫作工廠方法,咱們以上面 開篇一問裏的那個代碼爲例!
    • 當建立 UserServiceImpl的時候,會先存儲當前的方法對象也就是 UserServiceImpl的方法對象,也就是放置到 ThreadLocal裏面去!
    • 而後發現是一個代理對象,進入到代理邏輯,在代理邏輯裏面,走到這個判斷邏輯,發現本次攔截的方法和 ThreadLocal裏面的方法是一致的,而後就放行,開始調用真正的 userService()方法,執行這個方法的時候,方法內部調用了 testService();方法!
    • 發現 testService()又是一個代理對象,因而又走代理邏輯,而後走到這個判斷,判斷髮現當前攔截的方法是 testService而ThreadLocal裏面的方法倒是 userService,此時判斷就失敗了,因而就走到另一個分支!
    • 另一個分支就再也不執行這個方法了,而是直接去beanFactory去取這個bean,直接返回!
  2. return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);這個是當攔截的方法是工廠方法的時候直接放行,執行父類的邏輯,爲何是父類!Cglib是基於繼承來實現的,他的父類就是原始的那個沒有通過代理的方法,至關於調用 super.userService()去調用原始邏輯!
  3. resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);這個也是一會咱們要看的代碼邏輯,這個就是當判斷不成立,也就是發現工廠方法裏面還調用了另一個工廠方法的時候,會進入到這裏面!那咱們看一下這裏面的邏輯吧!

resolveBeanReference方法邏輯

private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
                                    ConfigurableBeanFactory beanFactory, String beanName)
 
{
  。。。。。。。。。忽略沒必要要代碼。。。。。。。。。
        //經過getBean從容器中拿到這個實例
        //這個beanFactory是哪裏來的,就是第一個攔截器裏面注入的`$$beanFactory`
        Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
                               beanFactory.getBean(beanName));

        。。。。。。。。。忽略沒必要要代碼。。。。。。。。。
        return beanInstance;
    }
 
}

這裏面的主要邏輯就是從beanFactory裏面獲取這個方法對應的bean對象,直接返回!而不是再去調用對應的方法建立!這也就是爲何屢次調用,返回的實例永遠只是一個的緣由!

6、總結

整個過程比較繞,讀者能夠本身跟着文章調試一下源碼,相信通過過深度思考,你必定有所收穫!

整個過程分爲兩大部分:

  1. 加強配置類
    • 檢測加了 @Configuration註解的配置類!
    • 建立代理對象(BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor)做爲加強器的回調方法!
    • 返回代理後的類對象!
    • 設置進配置類的beanClass!
  2. 建立bean
    • 一致的話就走原始的建立邏輯!
    • 不一致,就從bean工廠獲取!
    • 發現該bean建立的時候依附配置類(也就是加了@Bean的方法)!
    • 回調加強配置類的方法,並記錄該方法!
    • 判斷攔截的方法和記錄的方法是否一致
    • 返回建立好的bean

收工!

推薦閱讀:

  1. 據說你一讀Spring源碼就懵?我幫你把架子搭好了,你填就行!
  2. 萬字長文,助你深度遨遊Spring循環依賴源碼實現!
  3. 生氣!面試官你過來,我給你手寫一個Spring Aop實現!

才疏學淺,若是文章中理解有誤,歡迎大佬們私聊指正!歡迎關注做者的公衆號,一塊兒進步,一塊兒學習!



       
❤️「轉發」 「在看」 ,是對我最大的支持❤️



本文分享自微信公衆號 - JAVA程序狗(javacxg)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索