Spring Cloud源碼研讀(一):啓動與Bean加載

SpringBoot啓動核心邏輯

通常最簡單的springboot項目的總入口是如下寫法。類上加註解@SpringBootApplication,然後直接調用靜態方法SpringApplication#run(Class<?> primarySource,String… args):

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

註解@SpringBootApplication定義了一系列SpringBoot提供的自動化配置功能。@SpringBootApplication是個組合註解,核心內容如下:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
    
}

實際springboot的核心啓動邏輯在SpringApplication#run(String… args)實例方法中。這個代碼塊主要實現了以下功能:

①、通過SpringFactoriesLoader查找並加載所有的 SpringApplicationRunListeners,通過調用starting()方法通知所有的SpringApplicationRunListeners應用開始啓動了。

②、創建並配置當前應用將要使用的 Environment,Environment用於描述應用程序當前的運行環境,其抽象了兩個方面的內容:配置文件(profile)和屬性(properties)。

③、SpringBoot語法糖,應用在啓動時輸出Banner。

④、根據是否是web項目,來創建不同的ApplicationContext容器。

⑤、創建一系列 FailureAnalyzer。

⑥、初始化ApplicationContext。

⑦、調用ApplicationContext的 refresh()方法,完成IoC容器可用的最後一道工序。

⑧、查找當前context中是否註冊有CommandLineRunner和ApplicationRunner,如果有則遍歷執行它們。

⑨、執行所有SpringApplicationRunListener的finished()方法。

SpringBoot 創建ApplicationContext容器

①、在上述springboot啓動過程中,SpringApplicationRunListeners加載了一個EventPublishingRunListener對象實例。

②、在創建Environment後,EventPublishingRunListener發佈了一個ApplicationEnvironmentPreparedEvent事件,BootstrapApplicationListener#onApplicationEvent()訂閱了該事件。

③、BootstrapApplicationListener#onApplicationEvent()中,創建了一個新的SpringApplication和一個AnnotationConfigApplicationContext。並執行SpringApplication#run()。

④、SpringApplication#run()中啓動bean的掃描註冊和容器上下文的加載。

ApplicationContext初始化

上述第④簡化後的調用堆棧如下:

1. SpringApplication#run()
2. AbstractApplicationContext#refresh()
3. AbstractApplicationContext#invokeBeanFactoryPostProcessors()
4. PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
5. ConfigurationClassPostProcessor#processConfigBeanDefinitions(BeanDefinitionRegistry)
6. ConfigurationClassParser#parse(candidates)
7. ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass,sourceClass)

第2步AbstractApplicationContext#refresh()執行beanFactory完成初始化的完整步驟,直接複製代碼如下:

// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
//註冊需要加入beanFactory的所有bean
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();
//執行單例bean的實例化
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();

第5步在loadBeanDefinitions時,執行了ImportBeanDefinitionRegistrar,實現Bean的動態加載。完成了processConfigBeanDefinitions執行完成後,spring獲得了所有需要加載的bean候選集合Set。

5.1. ConfigurationClassPostProcessor.processConfigBeanDefinitions()
5.2. ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
5.3. ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars()
5.4. ImportBeanDefinitionRegistrar.registerBeanDefinitions()

第6步ConfigurationClassParser會對每一個bean遞歸查找內部的Bean聲明。

第7步ConfigurationClassParser#doProcessConfigurationClass()是spring bean註冊掃描核心類。在源代碼中作者給出了十分良心的註釋,直接複製粘貼如下:

7.1. // Process any @PropertySource annotations
7.2. // Process any @ComponentScan annotations
7.3. // Process any @Import annotations
7.4. // Process any @ImportResource annotations
7.5. // Process individual @Bean methods

此處@ComponentScan掃描的Bean包路徑就是基於@SpringBootApplication註解聲明的路徑。ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()。

7.3.1. ConfigurationClassParser.doProcessConfigurationClass(C)
7.3.2. ConfigurationClassParser.getImports(SourceClass)
7.3.3. ConfigurationClassParser.collectImports(SourceClass, Set<SourceClass>, Set<SourceClass>)
7.3.4. ConfigurationClassParser$SourceClass.getAnnotationAttributes(String, String)

第7.3.2步getImports()是springboot啓動非常關鍵的一步,通過sourceClass “MyApplication"的驅動,遞歸找到了AutoConfigurationImportSelector,從而實現了springboot的自動加載過程。

// 註解鏈
@SpringBootApplication
	=> @EnableAutoConfiguration
		=> @Import(AutoConfigurationImportSelector.class)

7.3導入@Import註解value時,分了三個類型處理:

  • @Configuration class:普通的配置bean,導入其內部bean聲明

  • ImportSelector:基於條件選擇加載不同的@Configuration class

  • ImportBeanDefinitionRegistrar:一般是工廠模式實現動態加載Bean

springboot自動配置過程總結

  • @EnableAutoConfiguration註解表示開啓Spring Boot自動配置功能,Spring Boot會根據應用的依賴、自定義的bean、classpath下有沒有某個類 等等因素來猜測你需要的bean,然後註冊到IOC容器中。
  • @Import註解用於導入類,並將這個類作爲一個bean的定義註冊到容器中,這裏它將把 EnableAutoConfigurationImportSelector作爲bean注入到容器中,而這個類會將所有符合條件的@Configuration配置都加載到容器中

案例點睛

@EnableJpaRepositories

在Spring Boot中,我們會經常遇到@EnableXXX用來激活我們某一個功能性的模塊,通過類註解激活後我們就能使用所激活的配置給我們帶來的功能。通過SpringBoot啓動過程的分析我們知道,@EnableXXX實際上是通過引入@Import註解從而進一步引入具體的配置文件。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(JpaRepositoriesRegistrar.class)
public @interface EnableJpaRepositories {
}

autoconfigure

在實際開發中,我們發現並沒有使用@EnableJpaRepositories,但也可以直接使用JPA相關的功能。這是由於AutoConfigurationImportSelector通過SpringFactoriesLoader動態加載了JpaRepositoriesAutoConfigureRegistrar,從而實現引入@EnableJpaRepositories。

# spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfigureRegistrar,\

引用傳遞:

@EnableAutoConfiguration
	=> JpaRepositoriesAutoConfiguration
		=> @Import(JpaRepositoriesAutoConfigureRegistrar.class)
			=> @EnableJpaRepositories
				=> JpaRepositoryFactoryBean

Bean的動態注入機制

在使用JPA時,我們只聲明瞭MyEntityJpaRepository的接口,沒有任何具體的實現類,但是在服務層我們可以注入該接口類型的bean並使用。這是由於JpaRepositoriesAutoConfigureRegistrar實現了ImportBeanDefinitionRegistrar,從而將所有包掃描路徑下的Repository接口都註冊爲bean,並通過JpaRepositoryFactoryBean動態生成具體的代理實現。

在這裏插入圖片描述

Spring bean實例化時間點

第一:如果你使用BeanFactory作爲Spring Bean的工廠類,則所有的bean都是在第一次使用該Bean的時候實例化。
第二:如果你使用ApplicationContext作爲Spring Bean的工廠類,則又分爲以下幾種情況:
(1):如果bean的scope是singleton的,並且lazy-init爲false(默認是false,所以可以不用設置),則 ApplicationContext啓動的時候就實例化該Bean,並且將實例化的Bean放在一個map結構的緩存中,下次再使用該Bean的時候, 直接從這個緩存中取
(2):如果bean的scope是singleton的,並且lazy-init爲true,則該Bean的實例化是在第一次使用該Bean的時候進行實例化
(3):如果bean的scope是prototype的,則該Bean的實例化是在第一次使用該Bean的時候進行實例化

第一種場景:前述流程”ApplicationContext初始化“第4步PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()。

List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
		beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
	if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
		currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
		processedBeans.add(ppName);
	}
}

第二種場景:前述流程”ApplicationContext初始化“第2步AbstractApplicationContext#refresh()中的finishBeanFactoryInitialization(beanFactory)。

Spring Boot 手動配置@Enable的祕密: http://www.jerome.xin/articles/spring-boot-enable-congfiure

Spring Data JPA 工作原理 : 自定義JpaRespository接口卻不用提供實現:https://blog.csdn.net/andy_zhang2007/article/details/84064862

SpringBoot啓動流程解析:https://www.cnblogs.com/trgl/p/7353782.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章