以前開發(fā)一個項目,要花費不少時間在搭建項目,配置文件上,到現(xiàn)在Spring Boot開箱即用,需要技術(shù)棧導(dǎo)入pom就可以了,技術(shù)變更帶來效率提示是巨大的。有時候我會疑惑,這一切如何得來的,Spring Boot怎么拋棄war部署,拋棄繁瑣xml配置。
閱讀本文章需要一定的Spring框架知識儲備,最后能了解Spring如何進行Bean初始化的,至少知道BeanDefinition之類的知識點,才能更好閱讀文章。下面代碼基于Spring Boot 2.7.2 、 Spring Cloud 2021.0.3。
先從項目啟動放入入口,每一個Spring Boot 項目都需要main入口都要調(diào)用SpringApplication.run開始
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//web 項目類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
webApplicationType是一個枚舉類,描述當(dāng)前項目web類型
NONE: 當(dāng)前項目不是一個web項目
SERVLET: 基于servlet api的傳統(tǒng)web項目
REACTIVE: Spring webFlux 響應(yīng)式web框架
deduceFromClasspath : 根據(jù)項目jar判斷當(dāng)前項目屬于上面哪個一個類型,后面創(chuàng)建Spring 上下文對象需要用到
getSpringFactoriesInstances:從給定接口從文件META-INF/spring.factories 使用類名去加載全類名,并且返回接口所有實現(xiàn)類, 配置文件格式如下
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
這個類似JVM的SPI機制,對于Spring為什么沒有使用SPI來 引入擴展實例,我猜SPI不滿足多構(gòu)造參數(shù)的實現(xiàn)類初始化,這里暫時將這種機制稱作:SpringFactoriesLoader加載。
BootstrapRegistryInitializer:用于初始化BootstrapRegistry的回調(diào)接口,在 使用BootstrapRegistry之前調(diào)用它。
ApplicationContextInitializer:在執(zhí)行Spring工廠類調(diào)用AbstractApplicationContext.refresh(Spring 工廠核心方法bean初始化)之前初始化ConfigurableApplicationContext的回調(diào)接口。主要是做一個配置文件設(shè)置、屬性設(shè)置。
ConfigurableApplicationContext 是一個SPI接口用于通過 配置方式初始化ApplicationContext 。Spring Boot作為Spring框架的集大成者上下文對象ApplicationContext往往根據(jù)不同環(huán)境有所區(qū)別的,這時很需要ApplicationContextInitializer這種接口,由不同組件根據(jù)自身情況去實現(xiàn)接口初始化上下文對象。
ApplicationContextInitializer接口
DelegatingApplicationContextInitializer: 通過環(huán)境變量 context.initializer.classes 類名,加載所有ConfigurdiableApplicationContext子類,實例化,排序執(zhí)行ApplicationContextInitializer接口(接口參數(shù))。
SharedMetadataReaderFactoryContextInitializer: 注冊CachingMetadataReaderFactoryPostProcessor 用于向容器注冊SharedMetadataReaderFactoryBean,用于緩存Spring加載資源
ContextIdApplicationContextInitializer: 初始化ContextId
ConfigurationWarningsApplicationContextInitializer:報告@ComponentScan配置錯誤信息輸入告警日志
RSocketPortInfoApplicationContextInitializer: 創(chuàng)建一個監(jiān)聽事件,將server.ports賦值到 local.rsocket.server.port
ServerPortInfoApplicationContextInitializer: 創(chuàng)建web事件監(jiān)聽: 發(fā)布server namespace網(wǎng)絡(luò)端口
ConditionEvaluationReportLoggingListener: 創(chuàng)建一個事件監(jiān)聽,spring初始化成功或失敗,打印相關(guān)信息。
ApplicationListener列表
EnvironmentPostProcessorApplicationListener: 監(jiān)聽ApplicationEnvironmentPreparedEvent事件,執(zhí)行EnvironmentPostProcessor 配置文件前置處理器,加載配置文件到ConfigurableEnvironment
AnsiOutputApplicationListener: 監(jiān)聽Spring剛啟動事件,從配置文件加載ansi配置。
LoggingApplicationListener: 加載日志相關(guān)配置進行初始化設(shè)置。
BackgroundPreinitializer: 通過多線程方式初始化Formatter、Validation、HttpMessageConverter、jackson、UTF-8設(shè)置。
DelegatingApplicationListener:從配置文件 key:context.listener.classes加載監(jiān)聽器類名并實例化注冊到容器中
ParentContextCloserApplicationListener: 監(jiān)聽父級容器關(guān)閉事件,并且將事件傳遞到子級逐級傳遞下取。
ClearCachesApplicationListener: 清除類加器緩存
FileEncodingApplicationListener: 檢測當(dāng)前系統(tǒng)環(huán)境的file.encoding和spring.mandatory-file-encoding設(shè)置的值是否一樣,如果不一樣的話,就會拋出一個IllegalStateException異常,程序啟動立馬停止
run方法
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
//調(diào)用BootstrapRegistryInitializer接口對上下文進行初始化
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 設(shè)置 java.awt.headless 缺失顯示設(shè)備需要CPU介入顯示
configureHeadlessProperty();
//獲取事件發(fā)布器實例,這里會將上面監(jiān)聽器實例裝進發(fā)布器,監(jiān)聽器類似事件消費者
SpringApplicationRunListeners listeners = getRunListeners(args);
//發(fā)布starting 事件
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//獲取所有啟動參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//創(chuàng)建配置文件對象
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//從配置文件中忽視bean
configureIgnoreBeanInfo(environment);
//Banner 配置 打印
Banner printedBanner = printBanner(environment);
//使用ApplicationContextFactory 初始化ApplicationContentx Spring 工廠
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//配置文件對象配置
//開始對applicationContext context 進行初始化
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context); // 調(diào)用refresh
//空方法
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
//調(diào)用所有 ApplicationRunner CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
run()Spring Boot框架啟動流程
- 獲取Java 命令行啟動參數(shù),從中提取Spring 配置參數(shù),轉(zhuǎn)換從對應(yīng)變量
- 創(chuàng)建配置文件對象ConfigurableEnvironment ,命令行中會有profile設(shè)置,所以要根據(jù)profile加載配置文件,在執(zhí)行配置文件事件
- 已經(jīng)加載好文件了,從環(huán)境變量中檢測是否存在配置spring.beaninfo.ignore,如果設(shè)置,寫入到ConfigurableEnvironment中
- 開始打印banner,平??吹礁鞣Nbanner就是在這里執(zhí)行
- 開始創(chuàng)建ConfigurableApplicationContext ,Spring 容器工廠上下文對象
- 對剛剛創(chuàng)建ConfigurableApplicationContext 調(diào)用ApplicationContextInitializer 進行屬性設(shè)置
- 啟動Spring 容器IOC、AOP
- 發(fā)布Spring啟動完成事件
- 從容器中所有ApplicationRunner CommandLineRunner在調(diào)用方法
在run方法里面就完成完成整個Spring容器啟動流程了,包括Spring Cloud加載也是這里完成的。下面詳細(xì)分析prepareEnvironment(),配置文件上下文如何初始化的
prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 根據(jù)webApplicationType 創(chuàng)建
// SERVLET => ApplicationServletEnvironment
//REACTIVE=> ApplicationReactiveWebEnvironment
// NONE => ApplicationEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 命令行可能會有profile,可以選擇那個profile,也會將命令行參數(shù)生成一個PropertySources
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 添加 configurationProperties PropertySource到propertySourceList 隊列最前面
ConfigurationPropertySources.attach(environment);
// 執(zhí)行所有SpringApplicationRunListener
listeners.environmentPrepared(bootstrapContext, environment);
// 將defaultProperties sources 移致隊尾
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 從配置文件對應(yīng)spring.main.* 屬性注入
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
//將類型轉(zhuǎn)換器設(shè)置到environment
environment = convertEnvironment(environment);
}
// 因為EnvironmentPostProcessor 可能加載到配置文件里,這時需要configurationProperties 放入第一
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment() 如果當(dāng)前environment如何為空,則會根據(jù)根據(jù)webApplicationType 類型選擇對應(yīng)類進行初始化。大家可能好奇environment怎么可能有值呢,接著玩下看,當(dāng)我分析Spring Cloud時你就會返回environment不需要創(chuàng)建了。
ps: Environment 內(nèi)部使用PropertySource區(qū)分不同配置文件,每一個源配置都有自己的名字,比如系統(tǒng)變量systemProperties、環(huán)境變量systemEnvironment等等。使用一個propertySourceList一個list將所有PropertySource保存起來,在隊列前面永遠(yuǎn)最優(yōu)先加載。

在上面寫過一個監(jiān)聽器EnvironmentPostProcessorApplicationListener,它處理environmentPrepared事件,使用SpringFactoriesLoader加載所有EnvironmentPostProcessor 前置處理器,其中之一ConfigDataEnvironmentPostProcessor就是去做讀取配置文件,里面還有很多邏輯處理,這里就不展開了,有興趣的同學(xué)自行去分析代碼。讀取文件本身也是根據(jù)環(huán)境變量來的,這里有幾個Spring內(nèi)置配置
- spring.config.location 設(shè)定加載文件路徑,沒有則是使用類路徑./、config/
- spring.config.additional-location: 加載外部文件路徑,這個可以spring.config.location 共存,優(yōu)先級最大
- spring.config.name 設(shè)定文件名前置,默認(rèn) application
上面這些變量都是從環(huán)境變量、系統(tǒng)變量中獲取的,當(dāng)然不會從配置文件讀取到。通過設(shè)定文件路徑、文件名這樣方式確定加載文件,加載文件規(guī)則如下
{spring.config.name}-
{extension}
- extension:文件名后綴 內(nèi)置支持4種,分別是: properties、yml、xml、yaml
看下ConfigurableApplicationContext 如何被初始化的
prepareContext
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 初始化 ConfigurableEnvironment
context.setEnvironment(environment);
//初始化resourceLoader ConversionService
postProcessApplicationContext(context);
//執(zhí)行上面從SpringFactoriesLoader加載 ApplicationContextInitializer 對ConfigurableApplicationContext 屬性設(shè)置
applyInitializers(context);
// 調(diào)用SpringApplicationRunListener.contextPrepared 事件
listeners.contextPrepared(context);
// 執(zhí)行BootstrapContextClosedEvent 事件
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
//下面添加特定單例對象,為Spring初始化bean IOC 處理必要的bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 這里已經(jīng)從配置文件加載 設(shè)置到自身屬性上了,這時設(shè)置給上下文對象
// allowCircularReferences 允許同名bean覆蓋 lazyInitialization 對所有bean使用懶加載
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 這個前置處理器主要作用就是將配置defaultProperties 移到隊尾
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// Load the sources 這里有啟動類
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 初始化 BeanDefinitionLoader 并將啟動類注冊成BeanDefinition
load(context, sources.toArray(new Object[0]));
// 所有監(jiān)聽器執(zhí)行contextLoaded 事件
listeners.contextLoaded(context);
}
在這里完成了Spring 容器初始化,下一步就是啟動了。
bean初始化
其實我一直很好奇@Configuration這個注入如何實現(xiàn)配置類,還有還么多Class要被Spring進行初始化,如何變成BeanDefinition最后變成bean。我確定從AbstractApplicationContext.refresh()debug,終于被我發(fā)現(xiàn)Spring魔法,在invokeBeanFactoryPostProcessors()
在執(zhí)行invokeBeanFactoryPostProcessors方法中回去獲取BeanDefinitionRegistryPostProcessor類型內(nèi)置對象,并且執(zhí)行所有實現(xiàn)類。
-
BeanDefinitionRegistryPostProcessor: 你可以理解成BeanDefinition注冊前置處理器,主要就是生成BeanDefinition,再還給容器。在Spring還沒有初始化bean時,這個接口運行實現(xiàn)類去初始化BeanDefinition再交還給Spring工廠對象,簡白點就是這個對象會創(chuàng)建BeanDefinition,交給Spring,后續(xù)進行初始化bean。下面要講解其中一個實現(xiàn)類ConfigurationClassPostProcessor
postProcessBeanFactory創(chuàng)建postProcessBeanFactory
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) { //這個方法只能執(zhí)行一次,通過記錄上下文id標(biāo)記執(zhí)行
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
// 解析Class 生成BeanDefinition
processConfigBeanDefinitions(registry);
}
/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
//candidateNames 為前期使用BeanDefinitionRegistry 添加進去單例對象,除了擁有Spring 工廠對象外還有
// SpringBoot main 啟動類 這里能起到作用就是Spring Boot main 函數(shù)
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// 檢查beanDef 是不是配置類,帶有@Configuration都算
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 這里不存在沒有配置類,只有配置@SpringBootApplication Class 就是一個配置類
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Parse each @Configuration class
// ConfigurationClassParser 看名字就知道,這是一個解析@Configuration 解析類
// 將解析Class 工作專門委派給parse去做了,解析后的結(jié)果會變成 ConfigurationClass
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do { //這里是一個循環(huán)
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();
//已經(jīng)將所有配置類全部解析出來 變成ConfigurationClass
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed); // 刪除已經(jīng)解析過
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses); //將所有ConfigurationClass 轉(zhuǎn)化BeanDefinition ,并注冊到容器中
alreadyParsed.addAll(configClasses); //添加已經(jīng)注冊過的,上面刪除對應(yīng)
processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
candidates.clear();
// 當(dāng)ConfigurationClassParser 解析出ConfigurationClass 就會大于candidateNames
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
// bd 就是一個配置類
// bd 已經(jīng)注冊到容器中,但是不是在ConfigurationClassParser 解析出來的結(jié)果,則說明bd并沒有通過解析生成
// 可能為第三方 BeanDefinitionRegistryPostProcessor 生成BeanDefinition,加入到candidates 再次進入循環(huán)中
//被ConfigurationClassParser 解析,可以生成更多BeanDefinition
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty()); // 當(dāng)所有BeanDefinition 都已經(jīng)被解析完了,循環(huán)就可以退出了
//下面省略
}
看完上面的代碼,ConfigurationClassPostProcessor就是Spring將帶有@Configuration 標(biāo)記Class經(jīng)過一系列處理生成BeanDefinition的機制。在@SpringBootApplication 中有個一個@EnableAutoConfiguration帶有@Import(AutoConfigurationImportSelector.class),這個會被ConfigurationClassPostProcessor解析加載。其中AutoConfigurationImportSelector使用SpringFactoriesLoader加載,會將所有@EnableAutoConfiguration的配置類全部都加載ClassName,可以讓Spring Boot 加載ScanPackage 基礎(chǔ)包路徑之外的配置類,再通過@ConditionalOnBean、@ConditionalOnProperty這類注解,根據(jù)Class、配置判斷是否進行解析。
也就是說Spring Boot一開始就已經(jīng)獲取到所有配置類,只有當(dāng)符合條件時才會進入解析、加載、實例化。
Spring Cloud
上面說了Spring Boot自動化配置接下來就是Spring Cloud方面,看了上面源碼,發(fā)現(xiàn)沒有代碼有關(guān)Spring Cloud,現(xiàn)在還不知道配置中心的配置如何作用到已經(jīng)開始運行Spring 容器中。在開始分析代碼之前,先簡單看一個例子

可以看到applicatioinContext 有一個父級上下文,而這個就是Spring Cloud 上下文對象??吹竭@個是不是很驚奇,這個父級上下文在哪里初始化的呢,從代碼角度去看了。
上面分析過ApplicationListener監(jiān)聽器中,在Spring Cloud lib jar中有一個實現(xiàn)類BootstrapApplicationListener,通過它來啟動Spring Cloud。
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
// 兩個條件 environment 配置 spring.cloud.bootstrap.enabled 或者某個類是否存在,其實就是 spring-cloud-starter-bootstrap jar class
// 配置 spring.config.use-legacy-processing 這個配置是用來兼容舊版本配置文件加載
//我這里環(huán)境引入spring-cloud-starter-bootstrap 第一個條件返回true,第二條件不用判斷
if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
return;
}
// don't listen to events in a bootstrap context
// 判斷environment 是否已經(jīng)存在bootstrap 文件,已經(jīng)加載過不需要往下執(zhí)行了
//當(dāng)父級初始化也會執(zhí)行監(jiān)聽器事件,到時來到這里時,父級監(jiān)聽器不會往下執(zhí)行了
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
// 默認(rèn)配置文件名,沒有在環(huán)境變量配置默認(rèn)就是bootstrap
String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) { //在已經(jīng)存在ParentContextApplicationContextInitializer 中返回父級容器
context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
}
}
if (context == null) { //當(dāng)上面ParentContextApplicationContextInitializer 沒有執(zhí)行就會走下面初始化父級容器方法
// 這里會返回父級容器,也就是Spring Cloud 上下文對象
context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
}
//從父級容器中獲取ApplicationContextInitializer 交給SpringApplication
//父級生成ApplicationContextInitializer 用于增強子類
apply(context, event.getSpringApplication(), environment);
}
這個監(jiān)聽器主要根據(jù)配置文件信息來啟動Spring Cloud組件,如果沒有相應(yīng)的配置根據(jù)項目環(huán)境來,看下Spring Cloud上下文如何被初始化出來的。
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
final SpringApplication application, String configName) {
ConfigurableEnvironment bootstrapEnvironment = new AbstractEnvironment() {
};
MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
String configAdditionalLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
// 使用代碼生成一個Spring Cloud加載文件的配置信息,規(guī)則類似上面加載applicaton 配置
bootstrapMap.put("spring.config.name", configName);
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
if (StringUtils.hasText(configAdditionalLocation)) {
bootstrapMap.put("spring.config.additional-location", configAdditionalLocation);
}
//將加載文件的配置信息放入配置文件上下文 environment
bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
// TODO: is it possible or sensible to share a ResourceLoader?
// SpringApplicationBuilder 為SpringApplication 包裝類,重新生成SpringApplication來創(chuàng)建ApplicationContext 上下文
SpringApplicationBuilder builder = new SpringApplicationBuilder().profiles(environment.getActiveProfiles())
.bannerMode(Mode.OFF).environment(bootstrapEnvironment)
// Don't use the default properties in this builder
.registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
if (builderApplication.getMainApplicationClass() == null) {
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
}
/ BootstrapImportSelectorConfiguration
builder.sources(BootstrapImportSelectorConfiguration.class);
// 這里將調(diào)用SpringApplication.run 上面已經(jīng)分析,
final ConfigurableApplicationContext context = builder.run();
context.setId("bootstrap");
//這里添加AncestorInitializer 是一個ApplicationContextInitializer 實現(xiàn)類,目的就是讓子applicationContext 和父級關(guān)聯(lián)起來
addAncestorInitializer(application, context);
//當(dāng)前environment 為子集配置對象,這里要刪除掉父級加載文件信息
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
//將 springCloudDefaultProperties 配置文件信息copy到environment 中
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
現(xiàn)在所有代碼都看完了,我們來理一理整一個流程就會清晰明了。
在BootstrapApplicationListener中會根據(jù)配置文件或者是項目環(huán)境jar來是否啟動加載bootstrap配置文件。先從生成加載Spring Cloud配置信息,
使用SpringApplicationBuilder來構(gòu)建SprAppingApplication對象,然后執(zhí)行SpringApplication.run 方法,這個代碼我們已經(jīng)分析過了,初始化Spring容器上下文對象,然后進入核心refresh方法執(zhí)行IOC。SpringApplicationBuilder構(gòu)造SpringApplication 中沒有像我們寫啟動類main方法,會設(shè)置啟動類Class。所以被ConfigurationClassPostProcessor解析BeanDefinition,并沒有@SpringApplication 這個注解,所以這個Spring Cloud 工廠沒有獲取到basepackae、@EnableAutoConfiguration這些東西。根據(jù)上面代碼知道Spring Cloud將BootstrapImportSelectorConfiguration 作為BeanDefinition交給ConfigurationClassPostProcessor,這樣父級容器只有加載BootstrapConfiguration標(biāo)記類,父級bean和子級bean相互隔離。這樣父級容器就可以去啟動與Spring Cloud有關(guān)的bean。當(dāng)Spring Cloud容器已經(jīng)完成bean初始化后,再來執(zhí)行SpringApplicaton.run 啟動Spring 容器創(chuàng)建。這樣在子級啟動之前已經(jīng)將配置中心的配置對應(yīng)的對象已經(jīng)創(chuàng)建出來了。再通過ApplicationContextInitializer接口將配置對象加載ConfigurableEnvironment中。
這里使用較短的篇幅來分析Spring Boot這個框架如何工作,站在自己的思維上,使用3個知識點來展示Spring Boot技術(shù)細(xì)節(jié)實現(xiàn)。第一個從SpringApplication.run了解Spring兩大工廠對象ConfigurableApplicationContext、ConfigurableEnvironment如何初始化處理出來的,配置文件如何被加載的,加載規(guī)則,知識點SpringFactoriesLoader機制,如果要做Spring Boot組件必須要這個。了解了Spring Boot ApplicationContextInitializer、ApplicationListener這些接口,還有SpringApplicationRunListener為整個Spring Boot事件監(jiān)聽器,對應(yīng)整個框架的不同階段處理。第二簡單分析了
Spring容器啟動時如何生成BeanDefinition的機制實現(xiàn)類:BeanDefinitionRegistryPostProcessor,了解了Spring Boot組件如何被加載、實例化,這個依賴啟動類的注解。最后Spring Cloud組件如何加載實例化,這個依賴于前面兩個。