spring-IOC容器原始碼分析(一)bean初始化流程

暱稱又又被用了發表於2018-11-19

在傳統的開發步驟中,如果一個類依賴於另一個類的例項才能完成任務的話,都需要開發者手動new一個例項。之後手動設定到任務類中。但spring的出現,為我們提供了一種稱為“依賴注入”(Dependency Inject)的機制。bean的例項化在spring container內部完成,開發者只需要從xml或java code來配置bean達到定製例項化bean。而且,可以讓我們通過註解的方式,為我們自動注入需要的依賴。
這樣,開發者只需要遵循面向介面來開發應用,把例項化具體類和注入依賴的步驟抽離出業務程式碼,達到解耦的要求。如果哪天需要使用其他的介面實現依賴,只需要將新的實現配置進spring container。其他業務程式碼無需更改。這篇文章將圍繞著spring是如何實現bean的例項化、如何實現依賴注入來展開,剖析其內部執行機制

測試用例

// 定義一個簡單的bean
public class Car {
    public String color;
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}

// 定義一個配置類,向Spring bean container註冊bean
@Configuration
public class Config {
    @Bean
    public Car car() {
        Car car = new Car();
        car.setColor("red");
        return car;
    }
}

// 編寫一個測試類來初始化spring bean container,再從其中獲取我們在Config類中定義的bean的例項
public class ContextTest {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        Car car = context.getBean(Car.class);
        System.out.println(car.getColor());
    }
}
複製程式碼

執行這個用例,spring bean container將在其內部對我們注入的bean進行例項化、初始化兩個步驟。由於這裡沒有定義bean的作用域,因此我們註冊的這個bean(即Car)將以單例的形式存在於container中。每一次呼叫context.getBean(Car.class)返回的都是同一個Car的例項。

原始碼分析

說明:以下的流程分析,只圍繞spring容器例項化singleton bean(即單例)的主流程進行

主流程

對於spring這樣一個比較複雜的框架,其中包含了各種繁雜的業務邏輯。如果一開始就直接深究到每一個細節,我們無法對spring container有一個比較全域性的觀感,因此在這裡先把整個主流程畫出來,建立一個全域性的藍圖。

spring-IOC容器原始碼分析(一)bean初始化流程

解析註冊BeanDefinition

註冊和解析BeanDefinition,發生在AnnotationConfigApplicationContext#register流程中,其方法內部使用了AnnotatedBeanDefinitionReader#register來實現BeanDefinition的解析和註冊;而且在例項化AnnotatedBeanDefinitionReader後,立即向container註冊了多個BeanPostProcessor的BeanDefinition(應用於bean的例項化過程)

準備BeanFactory

在AnnotationConfigApplicationContext內部,組合了DefaultListableBeanFactory。在prepareBeanFactory(beanFactory)方法的呼叫過程中,向beanfactory注入了環境變數、環境屬性等。而且注入了多個BeanPostProcessor。

呼叫BeanFactoryPostProcessor

到了這一步,此時的container已經註冊了一系列的BeanFactoryPostProcessor、BeanPostProcessor和應用層相關的bean的BeanDefinition(如當前測試用例的Car)。由於此時所有的bean(包括BeanFactoryPostProcessor、BeanPostProcessor已經應用層的bean)都是以BeanDefinition存在於container中,並未例項化。這就提供了一個機會,新增特定的BeanFactoryPostProcessor,讓spring在例項化bean之前,可以定製修改BeanDefinition中的一些資料(如常用的PropertyPlaceholderConfigurer,從外部properties檔案讀取配置,定義bean的屬性);
預設情況下,只呼叫了ConfigurationClassPostProcessor,作用:

  1. 解析配置類,將其定義的bean注入到beanfactory
  2. 利用ConfigurationClassEnhancer增強了配置類的功能
  3. 註冊BeanPostProcessor:ImportAwareBeanPostProcessor
註冊BeanPostProcessor

繼續上一步流程,處理完BeanDefinition之後,對前面流程中註冊到beanfactory中的BeanPostProcessor進行例項化,並新增到beanfactory中的BeanPostProcessor處理佇列中。經過這一步之後,beanfactory中的BeanPostProcessor佇列存在以下BeanPostProcessor:

  1. ApplicationContextAwareProcessor
  2. ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor
  3. PostProcessorRegistrationDelegate$BeanPostProcessorChecker
  4. CommonAnnotationBeanPostProcessor
  5. AutowiredAnnotationBeanPostProcessor
  6. RequiredAnnotationBeanPostProcessor
  7. ApplicationListenerDetector
真正例項化和初始化bean的流程

這個步驟是通過呼叫DefaultListableBeanFactory#preInstantiateSingletons()方法完成的。實現了預載入所有已註冊的bean,這也是ApplicationContext與BeanFactory實現類的區別,在BeanFactory實現類中,只有對一個bean呼叫getBean(beanname)方法之後才會進行bean的載入。而ApplicationContext則是直接觸發其內部的BeanFactory載入所有定義好的bean。
在載入bean的過程中,涉及到三個步驟:

  1. 例項化
  2. 填充屬性
  3. 初始化

例項化並初始化bean流程

主流程

spring-IOC容器原始碼分析(一)bean初始化流程

例項化

通過呼叫AbstractAutowireCapableBeanFactory#createBeanInstance來例項化bean(在這之前,spring提供了一個機會可以通過BeanPostProcessor來建立bean,而不是常規的bean例項化,跟AOP相關)。
例項化後,呼叫了MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition,涉及到的實現類有:

  1. CommonAnnotationBeanPostProcessor,解析類準備@Resource注入需要的InjectionMetadata
  2. AutowiredAnnotationBeanPostProcessor,解析類準備@Autowired注入需要的InjectionMetadata
  3. RequiredAnnotationBeanPostProcessor,空實現
  4. ApplicationListenerDetector,標記了該bean是否為單例
填充bean屬性

例項化bean之後,就需要為bean填充bean的屬性值了。這一步主要是通過呼叫:InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation: 可以直接對bean填充屬性,之後直接返回。忽略後續的所有填充行為。預設沒有進行這一步流程,而是不做任何處理,進入下一個流程

  1. ImportAwareBeanPostProcessor
  2. CommonAnnotationBeanPostProcessor
  3. AutowiredAnnotationBeanPostProcessor
  4. RequiredAnnotationBeanPostProcessor InstantiationAwareBeanPostProcessor#postProcessPropertyValues: 各種自動注入的解析,如@Resource、@Autowired等
  5. ImportAwareBeanPostProcessor
  6. CommonAnnotationBeanPostProcessor
  7. AutowiredAnnotationBeanPostProcessor
  8. RequiredAnnotationBeanPostProcessor 最後通過applyPropertyValues()進行bean的賦值
初始化

進行到這一步時,bean已經從BeanDefinition例項化為了bean的例項,並且填充了屬性。是時候進行一下從外部而來的初始化邏輯了(非BeanDefinition相關的)。主要是:

  1. Aware介面的注入
  2. BeanPostProcessor#postProcessBeforeInitialization處理
  3. InitializingBean#afterPropertiesSet實現類的初始化方法
  4. BeanPostProcessor#postProcessAfterInitialization處理
  5. 註冊DisposableBean#destroy,在關閉容器時銷燬bean的回撥操作

總結

以上我們梳理了spring container載入bean的主體流程,spring為我們提供了幾大擴充套件點:

  1. BeanFactoryPostProcessor
  2. BeanPostProcessor
  3. Aware介面實現
  4. InitializingBean#afterPropertiesSet
  5. DisposableBean#destroy 為我們提供了改造bean的契機,方便在實際開發過程中定製化bean的載入。常見的有:
  6. PropertyPlaceholderConfigurer作為BeanFactoryPostProcessor的間接實現類,在bean的載入過程中,讀取外部properties檔案,對已註冊的BeanDefinition進行修改,達到了從外部檔案配置bean的目的;
  7. 在資料庫連線池的bean定義上,我們都會用@PreDestroy指定一個銷燬的回撥來釋放資料庫連線資源

相關文章