天天看點

聊透spring @Configuration配置類

作者:貳師兄的屠宰場

本章節我們來探索Spring中一個常用的注解@Configuration。我們先來了解一下該注解的作用是:用來定義目前類為配置類。

 那啥是配置類啊,有啥用啊。這個我們得結合實際使用場景來說,通常情況下。加了@Configuration的配置類内部,都會包含一個或多個@Bean注解的方法。

為了簡化定義,在後續我們稱@Bean注解的方法為工廠方法。

 配置類的奧秘就在這裡,Spring會保證多次調用@Bean标注的工廠方法,不會重複産生新的對象,始終是同一個,這也貫徹了Spring的單例哲學。

 多次調用建立方法,産生的竟然是同一個對象,這貌似違背了程式設計的基礎原理。怎麼可能,一定是Spring做了什麼,那就跟随着貳師兄的腳步一起解開@Configuration的神秘面紗吧。

這裡大家很容易産生一個誤解,認為隻有在加了@Configuration的配置類中,使用@Bean,才能将自定義建立的對象放入Spring容器中。其實不然,在Spring中:萬物皆為配置類。在任何能夠被Spring管理的類(比如加了@Component的類)中,定義@Bean方法,都能将自定義對象放入到Spring容器,@Bean本身的能力和@Configuration無關哦。

1. @Configuration的作用

 我們先通過一個簡單的案例,看一下@Configuration的作用。

需要特别說明的是:@Configuration繼承了@Component,這意味着@Configuration擁有@Component的全部功能,這也正是隻加@Configuration,也能被Spring掃描并處理的原因。
  • 情況一:被@Component辨別
@Component
public class MarkedByComponent {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}

// 輸出資訊:
// A created...
// A created...
// B created...           
  • 情況二:被@Configuration辨別
@Configuration
public class MarkedByConfiguration {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}

// 輸出資訊:
// A created...
// B created...           
  • 結論

     通過上述輸出我們發現:在多次調用a()的情況下,被@Component辨別的類,A會被建立多次。而被@Configuration辨別的配置類,A隻會被建立一次。此時我們可以大膽猜測:在@Configuration辨別的配置類中,重複調用@Bean辨別的工廠方法,Spring會對建立的對象進行緩存。僅在緩存中不存在時,才會通過工廠方法建立對象。後續重複調用工廠方法建立對象,先去緩存中找,不直接建立對象。

這裡小夥伴可能有個疑惑,a()隻在b()被調用一次,為什麼說多次呢,其實除了在b()中聲明式調用,由于a()被@Bean辨別,Spring也會主動調用一次的哦。

2. @Configuration的原理分析

 上一章節,我們通過一個簡單的案例,了解@Configuration的作用:使@Bean辨別的工廠方法,不會重複建立bean對象。同時也大膽猜測了,Spring對@Configuration辨別的配置類,做了緩存處理,進而讓@Bean工廠方法,有了"幂等的能力"。本章節我們一起探索一下,這個增強的緩存邏輯,是怎麼做到的?

  要對一個類的功能進行增強。代理 這個詞是不是已經在腦海中呼之欲出了,是的,就是代理。Spring對@Configuration辨別的類做了代理,進而進行功能的增強。

 當然,現在我們離直接分析代理功能還有點遙遠,畢竟步子不能邁的太大,不然容易扯着蛋。一步步來,首先我們先看看Spring是如何解析@Configuration注解的,畢竟需要先找出來,才能代理增強嘛。

2.1 @Configuration解析

  對@Configuration的解析過程是在spring掃描類的時候進行的。這裡需要介紹一下,Spring把加了@Configuration注解的稱為全配置類,其他的稱為半配置類,兩者判别标準是:是否加了@Configuration注解。

// ConfigurationClassParser.java
@Nullable
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
    //... 省略部分非相關代碼

    // 解析是否加了@ComponentScan, 并進行掃描
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
          sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
          !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
       for (AnnotationAttributes componentScan : componentScans) {

          // 1: 解析ComponentScan配置資訊,完成掃描(掃描出來的類在beanDefinitionMap中)
          Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

          // 2: 周遊掃描出來的類,檢查是不是配置類(配置類需要繼續遞歸解析)
          for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
             BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
             if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
             }
             // 重點:判斷類是不是配置類, 并标注類屬性資訊(是否為全配置類、@Order值等)
             if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                // 3 如果掃描出來的類,是配置類,還需要遞歸處理掃描出來的配置類
                parse(bdCand.getBeanClassName(), holder.getBeanName());
             }
          }
       }
    }
    //... 省略部分非相關代碼

}

//ConfigurationClassUtils.java
// 全配置類标記值
public static final String CONFIGURATION_CLASS_FULL = "full";
// 半配置類标記值
public static final String CONFIGURATION_CLASS_LITE = "lite";

public static boolean checkConfigurationClassCandidate(
      BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
     //... 省略部分非相關代碼
      
    // 2.1 從注解資訊中, 擷取@Configuration的注解資訊(如存在,标記為為全配置類)
    Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
    if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
       beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    }
    
    // 有@Component,@ComponentScan,@Import,@ImportResource注解,被标記成半配置類
    else if (config != null || isConfigurationCandidate(metadata)) {
       beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
    else {
       return false;
    }

    //... 省略部分非相關代碼
}           

  我們梳理一下這部分代碼的邏輯:

  1. 擷取配置類上@ComponentScan(basePackages="xxx")注解,交給元件componentScanParser解析,該元件會掃描出對應包路徑下需要Spring處理的類,并封裝成BeanDefinition,同時傳回掃描的類定義資訊。
  2. 周遊掃描出的類,檢查是否為配置類,配置類需要繼續解析(配置類可能有需要處理的Spring功能)。
  3. 在判斷是否為配置類時,也會給類打上标簽,加了@Configuration的标記為全配置類,其他的标記為半配置類。辨別的方式就是在BeanDefinition的attributes屬性中加入XX.configurationClass:full辨別。

判斷是否為配置類時,不僅僅隻是加了@Configuration的為配置類,加了@Component,@ComponentScan,@Import,@ImportResource等注解的,也是配置類,隻是為半配置類而已。

上述我們說@Configuration的作用是标記為配置類,這裡看其實是不準确的,準确說應該是标記為全配置類。但是這是更内部的邏輯,通常來說,@Configuration标記的就是配置類,其他标記的為普通類。

  總結一下,在掃描階段,Spring會對掃描出來的類進行全配置類還是半配置類的辨別。當然這裡也僅僅是辨別出來,并沒有使用,這是在給後面生成代理對象做準備。

聊透spring @Configuration配置類
這裡我們不得不吐槽一下,作為業界标杆的Spring,在方法命名上也如此混亂,在checkConfigurationClassCandidate()這個表明check動作的方法裡,做了很多BeanDefinition屬性解析指派的操作,簡直是在混淆視聽啊有沒有。看來命名确實是程式設計界最大的難題啊。

2.2 全配置類增強

  在上一章節,我們分析了Spring對标注了@Configuration配置類的查找和解析過程,總結起來就是:判斷類上是否有@Configuration注解,有就是全配置類,沒有就是半配置類。對于全配置的增強,我們也反複提到過,是借助代理來實作的。具體的的調用邏輯我們一起來看一下。

//ConfigurationClassPostProcessor.java
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   //...省略部分代碼
   // 對全配置類進行增強
   this.enhanceConfigurationClasses(beanFactory);
}


public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
   // 1: 查找需要增強的配置類
   // 周遊已經注冊多的beanName
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
      // ...省略部分代碼
      // 1.2 查找全配置類,放入configBeanDefs
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         // ...省略部分代碼
         // 将符合條件的,放入configBeanDefs中
         configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
   }

   // 2: 對全配置類進行增強
   // 2.1 建立一個配置類的增強器
   ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
   for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      Class<?> configClass = beanDef.getBeanClass();
      // 2.2 對類進行增強,使用cglib單例
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
         if (this.logger.isTraceEnabled()) {
            this.logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                  "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
         }
         // 2.3 修改BeanClass為加強類,這裡之是以不使用加強對象,是因為還需要将加強類交給spring執行個體化
         beanDef.setBeanClass(enhancedClass);
      }
   }
}           

  我們梳理一下這部分代碼的邏輯:

  1. 查找需要增強的配置類,這裡直接找出類BeanDefinition的attributes屬性中XX.configurationClass辨別值為full的即可。
  2. 對配置類生成代理,進行功能增強。
  3. 修改BeanDefinition的beanClass屬性為代理類,後續Spring在産生執行個體時,使用的就是代理類了。
聊透spring @Configuration配置類

 這裡有個重點,就是生成代理類,Spring采用的是CGLIB增強的方式,下面我們順藤摸瓜,看看Spring是怎麼操作的。

2.3 生成代理類

// ConfigurationClassEnhancer.java
// 回調器清單
private static final Callback[] CALLBACKS = new Callback[] {
      new BeanMethodInterceptor(),
      new BeanFactoryAwareMethodInterceptor(),
      NoOp.INSTANCE
};

// 回調過濾器
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);


//為原始類生成代理類
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
   // 1: newEnhancer() 生成生成CGLIB執行個體
   // createClass() 生成代理類
   Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));

   // 2: 傳回增強後的類
   return enhancedClass;
}

// 生成CGLIB執行個體
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
   Enhancer enhancer = new Enhancer();
   enhancer.setSuperclass(configSuperClass);
   // 設定接口為EnhancedConfiguration
   enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
   enhancer.setUseFactory(false);
   enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
   // 給代理類生成一個$beanFactory字段
   enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
   // 設定回調過濾器為EnhancedConfiguration,能根據傳入
   enhancer.setCallbackFilter(CALLBACK_FILTER);
   enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
   return enhancer;
}

// 生成代理類
private Class<?> createClass(Enhancer enhancer) {
   Class<?> subclass = enhancer.createClass();
   // 增加攔截器
   Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
   return subclass;
}           

  我們直接來看代碼,這裡其實大量運用了CGLIB相關的知識,感興趣的小夥伴可以自行百度補課,不補也沒關系,這裡會帶大家簡單了解一下:

  1. 先是建立了一個增強器Enhancer,為增強器設定了相關屬性,我們看一下核心屬性:
  • superclass: 父類,也就是被代理類,CGLIB的原理是為被代理類生成子類,進而實作功能增強。
  • interfaces: 實作的接口,這裡硬性指定為EnhancedConfiguration。(後續會使用該辨別)
  • namingPolicy:設定代理類的名稱政策,預設為BySpringCGLIB,這也是Spring生成的代理類類型包含xxBySpringCGLIB的原因。
  • strategy:生成政策,這是設定的BeanFactoryAwareGeneratorStrategy,預設邏輯是給代理類動态增加$beanFactory字段(後續會使用)。
  • callbackFilter:回調過濾器,在CGLib回調時可以設定不同方法執行不同的回調邏輯,或者根本不執行回調。回調過濾器的功能就是對執行方法選擇合适的攔截器。這裡一定要區厘清楚攔截器和回調過濾器的功能:

攔截器Callback:在調用目标方法時,CGLib會調用攔截器進行調用攔截,來實作增強的代理邏輯,當然裡面可以對應原始方法(在父類中)。

回調過濾器CallbackFilter:為執行方法選擇攔截器,CGLIB可以設定多個攔截器,然後根據具體執行的方法再進行選擇分發。

  1. 借助增強器Enhancer,生成代理類,并注冊攔截器。
  2. 傳回代理類。
聊透spring @Configuration配置類

2.3.1 攔截器的選擇

  在上述過程,我們已經知道Spring為标注了@Configuration配置類生成了代理類,并指定了回調過濾器和攔截器,同時将生成對象的類型修改為了代理類。那Spring在執行個體化的時候,執行個體化的就是代理對象了。在配置類中工廠方法(@Bean标注的方法)調用的時候,會先通過回調過濾器選擇合适的攔截器,然後再由攔截器進行攔截、并實作功能增強。

//ConfigurationClassEnhancer.ConditionalCallbackFilter.java

// 根據方法資訊,選擇攔截器
@Override
public int accept(Method method) {
  for (int i = 0; i < this.callbacks.length; i++) {
     Callback callback = this.callbacks[i];
     // 重點:攔截器的選擇, BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor繼承了ConditionalCallback,NoOp.INSTANCE 沒有繼承
     if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {
        return i;
     }
  }
  throw new IllegalStateException("No callback available for method " + method.getName());
}


//ConfigurationClassEnhancer.BeanFactoryAwareMethodInterceptor.java
// 比對邏輯: 隻處理setBeanFactory方法
@Override
public boolean isMatch(Method candidateMethod) {
   return isSetBeanFactory(candidateMethod);
}

public static boolean isSetBeanFactory(Method candidateMethod) {
   return (candidateMethod.getName().equals("setBeanFactory") &&
         candidateMethod.getParameterCount() == 1 &&
         BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
         BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}

//ConfigurationClassEnhancer.`BeanMethodInterceptor.java
// 比對邏輯: 隻處理方法上加了@Bean注解的,并且不是setBeanFactory()
@Override
public boolean isMatch(Method candidateMethod) {
   return (candidateMethod.getDeclaringClass() != Object.class &&
         !BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
         BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}           

  通過上述源碼,我們可以快速找到回調過濾器選擇攔截器的邏輯: if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)),我們分析一下這個判斷的條件,翻譯過來就是:

  • 如果是ConditionalCallback類型的攔截器,直接通過回調器isMatch()來判斷。
  • 如果不是ConditionalCallback類型的攔截器,則直接判斷為比對成功。

  初看可能有點懵,我們結合傳遞進來的攔截器來看,攔截器清單是:BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor、NoOp.INSTANCE,其中前兩個都是ConditionalCallback類型的,最後一個則是預設的空實作。同時傳遞的回調過濾器清單,是個有序數組,是以會優先比對BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor,如果這兩個都不能比對,則預設比對到空實作NoOp.INSTANCE。

聊透spring @Configuration配置類
關于BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor的比對邏輯,在代碼注釋中已經有明确的說明,比較清晰,大家注意看哦。

2.3.2 攔截器的功能

  經過上述的不厭其煩的啰嗦,我們已經知道,Spring為标注了@Configuration配置類生成了代理類,在調用配置類的方法時,會先通過回調過濾器選擇攔截器,然後由攔截器對方法進行增強。同時也清楚主要是靠BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor這兩個攔截器器發揮作用,本章節我們一起看一下這兩個攔截器的功能。

  在分析這兩個攔截器的功能之前,我們先來回憶一下@Configuration配置類的作用,讓加了@Bean的方法有了"幂等的能力",不會重複建立對象。

這裡小夥伴需要注意哦,@Bean注解本身的能力就是把我們自己産生的對象,放入到Spring容器中,便于我們依賴注入,這個@Configuration無關,在半配置類下該功能也是正常的哦。@Configuration的加持,隻是使其有了幂等性哦。

  現在我們已經知道@Configuration的作用,實作原理我們也清楚,通過CGLIB生成代理子類,具體的實作我們猜想是:把@Bean工廠方法生成的對象先放入到Spring容器中緩存,重複調用的時候,從緩存中直接擷取。實際上也确實如此,Spring就是這麼做的。

 OK,現在一切都真相大白,隻剩下最後一層神秘的面紗沒有解開,就是Spring怎麼做到的。要想達到這樣的效果,其實有兩個問題需要先解決:

  1. 對@Bean的工廠方法産生的對象,能夠去Spring容器中查重,不存在則生成對象,存則在直接擷取的。
  2. 注入Spring容器,也就是beanFactory,因為需要到容器中查重、擷取,是以需要先容器對象。

 Spring正是用BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor這兩個攔截器,來解決這兩個問題的。我們先來看注入Spring容器beanFactory的實作。

2.3.2.1 給代理類注入bean容器

 為了實作@Bean方法的"幂等",我們需要依賴beanFactory,可是沒有啊,怎麼辦呢,要求程式員使用@Autowired BeanFactory 手動放一個?手動是不可能,這輩子都是不可能的,主要是丢不起這個人。手動不行,那就自動放進來吧,這也就是為什麼Spring在生産的代理類中,強行加了一個beanFactory屬性的原因。

  • 為代理類增加beanFactory屬性
//ConfigurationClassEnhancer.java
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
   Enhancer enhancer = new Enhancer();
   // ...省略其他字段的指派
   // 給代理類生成一個$beanFactory字段
   enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
   return enhancer;
}


private static class BeanFactoryAwareGeneratorStrategy extends
      ClassLoaderAwareGeneratorStrategy {

   public BeanFactoryAwareGeneratorStrategy(@Nullable ClassLoader classLoader) {
      super(classLoader);
   }

   @Override
   protected ClassGenerator transform(ClassGenerator cg) throws Exception {
      // 動态添加字段"$beanFactory"
      ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
         @Override
         public void end_class() {
            declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
            super.end_class();
         }
      };
      return new TransformingClassGenerator(cg, transformer);
   }
}           

 通過源碼,我們可以看到,在生成子類位元組碼的時候,就借助CGLIB的能力,給代理類動态增加了一個屬性$beanFactory,其類型正是BeanFactory類型,這就是自動放入的。

聊透spring @Configuration配置類
  • 使用攔截器BeanFactoryAwareMethodInterceptor給beanFactory屬性指派  上述過程隻是增加了屬性$beanFactory,還沒有指派呢。其實在屬性填充的時候,會進行指派。我們一起看一下是如何指派的。
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
   // 給$beanFactory字段指派
   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");
      // 通過反射API,為該字段注入spring容器beanFactory
      field.set(obj, args[0]);
      
      if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
         // 如果注解配置類的父類實作了BeanFactoryAware,傳入參數BeanFactory,并執行父類方法
         return proxy.invokeSuper(obj, args);
      }
      return null;
   }
}           

 通過源碼,我們可以看到,直接通過反射給$beanFactory字段指派,那麼在後續其他流程中,就可以正常使用了,so happy。

聊透spring @Configuration配置類
其實這裡并不happy,原理和實作,我們都知道了,但是大家有沒有疑惑,這個攔截器中直接從參數args[0]擷取了beanFactory,為什麼?傳遞進來的參數到底是什麼?又是什麼時候回調這個攔截器的。筆者在網上搜了不少文章,好像并沒有人提及或者說清這個事情,可能确實有點難度吧,貳師兄仔細研究了下,限于篇幅和對Spring整體體系的限制,本節我們不展開,末尾寫個選讀吧,大家可以自行決定是否挑戰一下哦。

2.3.2.2 保證@Bean工廠方法"冥等"

// 測試代碼
@Configuration
public class MarkedByConfiguration {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}           

 我們根據測試案例中代碼b()->a(),來看一下攔截器BeanMethodInterceptor是如何保證幂等的。我們還是先看源碼,有個整體印象:

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

   public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
            MethodProxy cglibMethodProxy) throws Throwable {

      // 從增強後的注解配置類執行個體中,擷取BeanFactory(也就是成員變量$beanFactory)
      ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

      // ① 判斷目前調用到的方法,是否是正在執行建立的@Bean工廠方法
      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
         // 如果目前執行的是工廠方法來執行個體化bean,那就執行父類中的方法
         return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
      }

      // ② 如果目前執行的方法不是工廠方法,此時就去spring容器中擷取@Bean對應的bean執行個體
      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
   }
   
   private boolean isCurrentlyInvokedFactoryMethod(Method method) {
       // 擷取執行中的@Bean方法,在執行@Bean方法的建立時,會将目前@Bean方法放入ThreadLocal中,這裡再取出來
       Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
       // 判斷執行的@Bean方法,和正在回調的方法是否相同
       return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
         Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
    }

}           

 這裡我們隻分析核心方法,第一步就是 if (isCurrentlyInvokedFactoryMethod(beanMethod))這個判斷邏輯:

  1. 擷取目前執行的工廠方法(@Bean方法)。在Spring執行b()工廠方法時,就會将b()放入到ThreadLocal中,辨別該工廠方法正在建立中。
  2. 比對目前正在執行的工廠方法,和目前執行的方法,是否是同一個方法。這裡需要注意差別,比如在執行b()時,執行到第一行a()的調用了,進入a()方法後,目前執行的方法是a(),正在執行的@Bean工廠方式仍然是b(),兩者肯定不是同一個方法,傳回的就是false。

 了解了這個方法的判斷邏輯,下面我們再來分析執行b()的具體流程:

  1. 執行到b方法時,将b()放入到ThreadLoacl中,辨別正在建立。
  2. 執行b(),由于此時b()所在的配置類,已經為代理類執行個體了,是以會執行攔截器的回調,且b()是@Bean修飾的工廠方法,回調過濾器會選擇BeanMethodInterceptor,并執行intercept()攔截邏輯。
  3. 執行isCurrentlyInvokedFactoryMethod()判斷,也就是①的代碼,此時ThreadLocal中是b(),正在執行的也是b(),二者相同,條件成立,需要執行②處的代碼。
  4. 開始執行父類中的方法,也就是原始的,我們自己編寫的方法。注意:此時已經從攔截器的方法中跳出來了,執行的是原始的代碼。根據執行的順序,首先執行的就是a()的建立。
  5. 執行a(),由于此時a()所在的配置類,也是代理類執行個體,且為被@Bean辨別的工廠,是以也會被BeanMethodInterceptor攔截,此時也會執行isCurrentlyInvokedFactoryMethod()判斷,但是由于此時ThreadLocal中擷取到的方法還是b(),而正在執行的方法是a(),二者不同,需要執行③處的代碼。
  6. ③處的代碼邏輯,主要是去容器中擷取bean執行個體,如果不存在,會先建立bean,放入容器後傳回。這不是我們讨論的核心問題,我們假設a()之前已經執行過了,在容器中是存在的,此時直接傳回了a對象,并沒有再次建立。
  7. 繼續執行原始a()的第二行及後面的代碼,無關我們的分析,可以忽略。

  我們在代碼中加入注釋,讓大家加深一下印象:

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

   public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
            MethodProxy cglibMethodProxy) throws Throwable {

      // 從增強後的注解配置類執行個體中,擷取BeanFactory(也就是成員變量$beanFactory)
      ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

      // ① 判斷目前調用到的方法,是否是正在執行建立的@Bean工廠方法
      // 1.1: b()執行時,已經将y放入ThreadLocal中,這裡擷取到的是b,條件成立
      // 2.1 執行到b()調用a()了,a()也被代理了,也會執行到這裡,此時ThreadLocal中是y,條件不成立,執行②
      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
         // 1.2 執行父類中的方法,也就是@Bean工廠方法的邏輯,會調用a()方法呢
         return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
      }

      // ② 如果目前執行的方法不是工廠方法,此時就去spring容器中擷取@Bean對應的bean執行個體
      // 2.2 去Spring容器中查找a對象,不存在會執行個體化的,這個可以傳回
      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
   }

}           
聊透spring @Configuration配置類

 在上面的流程分析時。我們發現,該攔截器确實做到了不會重複建立@Bean工廠方法産生的bean,核心原理是對@Bean方法的每次調用都會攔截,然後先去容器查重,存在直接傳回。正是這個操作,保證了@Bean工廠方法的幂等。

聊透spring @Configuration配置類

 至此,我們已經明白了在@Configuration配置類的加持下,為什麼@Bean工廠方法可以保持幂等的秘密。希望小夥伴們能有所收獲,這是對貳師兄最大的安慰哈。

3. 增強代碼的調用(選讀)

3.1 BeanFactoryAwareMethodInterceptor的調用

  上述在提到代理類中,動态添加的屬性$beanFactory是通過攔截器BeanFactoryAwareMethodInterceptor反射指派的。并且在攔截器BeanMethodInterceptor直接使用了。但是沒有提提及BeanFactoryAwareMethodInterceptor的被調用時機和調用參數,原因是需要小夥伴對Spring的生命周期和Bean的生命周期需要比較熟悉的了解。大家需要補充對應的知識。

關于Bean的生命周期,聊透Spring bean的生命周期有着比較詳細的分析。但是對Spring的生命周期,還沒有來得及分析,感興趣的小夥伴,可以留言催更哦。
  • 注冊BeanPostProcessor,觸發攔截器

  在上述的分析中,我們已經知道,對于全配置類生成代理子類的邏輯在ConfigurationClassPostProcessor#postProcessBeanFactory()中,該方法除了生成代理類外,還手動添加了ImportAwareBeanPostProcessor這個Bean的後置處理器,該後置處理器的作用就是調用setBeanFactory方法(會被攔截器BeanFactoryAwareMethodInterceptor攔截,進而執行攔截器的内容),最後為屬性$beanFactory指派。

//ConfigurationClassPostProcessor.java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   // ...其他代碼
   // 對全配置類進行增強
   enhanceConfigurationClasses(beanFactory);
   // 手動添加一個BeanPostProcessor,用來設定setBeanFactory
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}


private static class ImportAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
    @Override
    public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
        // 調用setBeanFactory()
        if (bean instanceof EnhancedConfiguration) {
            ((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
        }
        return pvs;
    }
}           

  我們可以看到,該後置處理器postProcessProperties()的邏輯就是調用setBeanFactory(),并且隻調用類型是EnhancedConfiguration子類的哦。小夥伴們還記不記得,Spring為全配置類生成的代理類,手動加了實作EnhancedConfiguration接口的哦,這不就對上了嘛,你說巧不巧。

  還有小夥伴需要注意,該後置處理器一旦調用,執行到setBeanFactory()時,就會被BeanFactoryAwareMethodInterceptor攔截,此時傳遞的參數是this.beanFactory,也就是Spring的bean容器,是以在攔截器直接args[0]反射指派,才是沒有問題的。

  • ImportAwareBeanPostProcessor後置處理器的調用  
  • 現在我們已經知道通過後置處理器ImportAwareBeanPostProcessor進行setBeanFactory()的回調,也知道傳遞的參數是Spring的bean容器。那麼問題又來了,這個後置處理器是什麼時候調用,畢竟他調用了,攔截器才能執行,屬性$beanFactory才能被指派啊。

 好煩啊,有沒有,沒辦法,Spring的代碼就是這樣,功能實作的鍊路很長,也很分散,一環扣一環,想串聯起來很難,這也是筆者寫這個系列文章的初衷,想給大家整合起來,明白來龍去脈,這樣一來文章篇幅勢必很長,沒辦法的,大家忍耐一下。好了,不啰嗦了,我們繼續。

  仔細看筆者聊透Spring bean的生命周期這篇文章的小夥伴可能有印象,在屬性填充這一步,回調的後置處理器,有ImportAwareBeanPostProcessor,我們在一起看一下:

聊透spring @Configuration配置類

  OK,現在這個這個鍊路也清晰了,我們一起總結一下:

聊透spring @Configuration配置類

3.2 BeanMethodInterceptor的調用

  關于@Bean工廠方法的調用時機,其實是在bean的生命周期的初始化bean階段,不清楚的小夥伴自行檢視吧。啰嗦不動了。

  由于BeanFactoryAwareMethodInterceptor攔截器的調用在bean的生命周期的屬性填充階段,BeanMethodInterceptor的調用在初始化bean階段,是以是能保證BeanFactoryAwareMethodInterceptor攔截器的觸發早于BeanMethodInterceptor攔截器的,這也是BeanMethodInterceptor能夠直接使用的原因。

 最後我們一起總結一下吧,希望大家多學習,多進步,最後祝大家新年快樂,發大财。

聊透spring @Configuration配置類

繼續閱讀