天天看點

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

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

一、前言

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

之前說過學習源碼的目的在哪?正如我特别喜歡的一句話,

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

,對于Spring的了解僅僅局限于使用遠遠不夠,Spring作為一個國内絕大多數java開發者使用的一個項目管理架構,他是一個生态,什麼是生态?比如現在的

SpringBoot

SpringCloud

,他們是什麼?是Spring生态中的一個組成部分!他們利用Spring生态中提供的各種擴充點,一步一步的封裝,成就了現在Spring

快速啟動

自動配置

等亮眼的功能!作為Spring的使用者,我們理應了解Spring的實作和各種擴充點,進而能夠真正的深入Spring生态!深入了,再去研究所學生态中的組成部分如:

SpringBoot

之流的架構,也就水到渠成了!

二、開篇一問

相信大部分開發者對于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

會被執行個體化幾次?

相信有不少同學,張口就說

一次

,對,沒錯,但是為什麼呢?我當時對這裡的問題深深的感到自我懷疑!甚至一度懷疑自己的java基礎,明明這裡調用了另外一個方法,但是為什麼沒有進行兩次執行個體化呢?

我問了很多同僚、朋友,他們隻知道這樣寫是沒有問題的!但是具體原因不知道!為什麼呢?我們帶着這個問題往下看!

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

我們從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中眼見為虛的 @Configuration 配置類

被代理的Spring配置類

果然,他不是他了,他被(玷污)代理了,而且使用的代理是

cglib

,那麼這裡就可以猜測一個問題,在Bean方法中調用另外一個Bean方法,他一定是通過代理來做的,進而完成了多次調用隻執行個體化一次的功能!

到這裡,解決了,原來是這樣!那麼現在有兩個疑問:

  1. 什麼時候給配置類加的代理?
  2. 代理邏輯裡面是怎麼完成多次調用傳回同一個執行個體的功能的?

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

四、代理圖示

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

cglib代理配置類的流程圖

這張圖我放出來,如果你沒有了解過的話,一定是很迷惑,沒關系,後面會用源碼解釋,而且源碼看完之後,我們會大概手寫一個,幫助你了解!

五、源碼詳解

不妨猜一下,看過我以前的文章的讀者都應該了解!Spring建立bean執行個體的時候,所需要的資訊是在

beanDefinitionMap

裡面存放的,那麼在初始化的時候解析bean的bd的時候,一定是替換了配置類bd裡面的類對象,才會使後面執行個體化config的時候變成了一個代理對象,是以我們的入口應該在這裡:

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

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傳回的一個重要邏輯,為什麼?因為

    EnhancedConfiguration

    BeanFactoryAware

    的子類,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 MethodInterceptor, ConditionalCallback {

    @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中眼見為虛的 @Configuration 配置類

被代理的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對象,直接傳回!而不是再去調用對應的方法建立!這也就是為什麼多次調用,傳回的執行個體永遠隻是一個的原因!

六、總結

整個過程比較繞,讀者可以自己跟着文章調試一下源碼,相信經過過深度思考,你一定有所收獲!

整個過程分為兩大部分:

1.增強配置類

  • 檢測加了

    @Configuration

    注解的配置類!
  • 建立代理對象(BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor)作為增強器的回調方法!
  • 傳回代理後的類對象!
  • 設定進配置類的beanClass!

2.建立bean

  • 一緻的話就走原始的建立邏輯!
  • 不一緻,就從bean工廠擷取!
  • 發現該bean建立的時候依附配置類(也就是加了@Bean的方法)!
  • 回調增強配置類的方法,并記錄該方法!
  • 判斷攔截的方法和記錄的方法是否一緻
  • 傳回建立好的bean

收工!