以前開發一個項目,要花費不少時間在搭建項目,配置文件上,到現在Spring Boot開箱即用,需要技術棧導入pom就可以了,技術變更帶來效率提示是巨大的。有時候我會疑惑,這一切如何得來的,Spring Boot怎麼拋棄war部署,拋棄繁瑣xml配置。
閱讀本文章需要一定的Spring框架知識儲備,最後能瞭解Spring如何進行Bean初始化的,至少知道BeanDefinition之類的知識點,才能更好閱讀文章。下面代碼基於Spring Boot 2.7.2 、 Spring Cloud 2021.0.3。
先從項目啓動放入入口,每一個Spring Boot 項目都需要main入口都要調用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是一個枚舉類,描述當前項目web類型
NONE: 當前項目不是一個web項目
SERVLET: 基於servlet api的傳統web項目
REACTIVE: Spring webFlux 響應式web框架
deduceFromClasspath : 根據項目jar判斷當前項目屬於上面哪個一個類型,後面創建Spring 上下文對象需要用到
getSpringFactoriesInstances:從給定接口從文件META-INF/spring.factories
使用類名去加載全類名,並且返回接口所有實現類, 配置文件格式如下
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
這個類似JVM的SPI機制,對於Spring爲什麼沒有使用SPI來 引入擴展實例,我猜SPI不滿足多構造參數的實現類初始化,這裏暫時將這種機制稱作:SpringFactoriesLoader加載。
BootstrapRegistryInitializer:用於初始化BootstrapRegistry的回調接口,在 使用BootstrapRegistry之前調用它。
ApplicationContextInitializer:在執行Spring工廠類調用AbstractApplicationContext.refresh(Spring 工廠核心方法bean初始化)之前初始化ConfigurableApplicationContext的回調接口。主要是做一個配置文件設置、屬性設置。
ConfigurableApplicationContext 是一個SPI接口用於通過 配置方式初始化ApplicationContext 。Spring Boot作爲Spring框架的集大成者上下文對象ApplicationContext往往根據不同環境有所區別的,這時很需要ApplicationContextInitializer這種接口,由不同組件根據自身情況去實現接口初始化上下文對象。
ApplicationContextInitializer接口
DelegatingApplicationContextInitializer
: 通過環境變量 context.initializer.classes
類名,加載所有ConfigurdiableApplicationContext子類,實例化,排序執行ApplicationContextInitializer接口(接口參數)。
SharedMetadataReaderFactoryContextInitializer
: 註冊CachingMetadataReaderFactoryPostProcessor 用於向容器註冊SharedMetadataReaderFactoryBean,用於緩存Spring加載資源
ContextIdApplicationContextInitializer
: 初始化ContextId
ConfigurationWarningsApplicationContextInitializer
:報告@ComponentScan配置錯誤信息輸入告警日誌
RSocketPortInfoApplicationContextInitializer
: 創建一個監聽事件,將server.ports賦值到 local.rsocket.server.port
ServerPortInfoApplicationContextInitializer
: 創建web事件監聽: 發佈server namespace網絡端口
ConditionEvaluationReportLoggingListener
: 創建一個事件監聽,spring初始化成功或失敗,打印相關信息。
ApplicationListener列表
EnvironmentPostProcessorApplicationListener
: 監聽ApplicationEnvironmentPreparedEvent事件,執行EnvironmentPostProcessor 配置文件前置處理器,加載配置文件到ConfigurableEnvironment
AnsiOutputApplicationListener
: 監聽Spring剛啓動事件,從配置文件加載ansi配置。
LoggingApplicationListener
: 加載日誌相關配置進行初始化設置。
BackgroundPreinitializer
: 通過多線程方式初始化Formatter、Validation、HttpMessageConverter、jackson、UTF-8設置。
DelegatingApplicationListener
:從配置文件 key:context.listener.classes加載監聽器類名並實例化註冊到容器中
ParentContextCloserApplicationListener
: 監聽父級容器關閉事件,並且將事件傳遞到子級逐級傳遞下取。
ClearCachesApplicationListener
: 清除類加器緩存
FileEncodingApplicationListener
: 檢測當前系統環境的file.encoding和spring.mandatory-file-encoding設置的值是否一樣,如果不一樣的話,就會拋出一個IllegalStateException異常,程序啓動立馬停止
run方法
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
//調用BootstrapRegistryInitializer接口對上下文進行初始化
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 設置 java.awt.headless 缺失顯示設備需要CPU介入顯示
configureHeadlessProperty();
//獲取事件發佈器實例,這裏會將上面監聽器實例裝進發布器,監聽器類似事件消費者
SpringApplicationRunListeners listeners = getRunListeners(args);
//發佈starting 事件
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//獲取所有啓動參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//創建配置文件對象
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); // 調用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);
//調用所有 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 命令行啓動參數,從中提取Spring 配置參數,轉換從對應變量
- 創建配置文件對象ConfigurableEnvironment ,命令行中會有profile設置,所以要根據profile加載配置文件,在執行配置文件事件
- 已經加載好文件了,從環境變量中檢測是否存在配置spring.beaninfo.ignore,如果設置,寫入到ConfigurableEnvironment中
- 開始打印banner,平常看到各種banner就是在這裏執行
- 開始創建ConfigurableApplicationContext ,Spring 容器工廠上下文對象
- 對剛剛創建ConfigurableApplicationContext 調用ApplicationContextInitializer 進行屬性設置
- 啓動Spring 容器IOC、AOP
- 發佈Spring啓動完成事件
- 從容器中所有ApplicationRunner CommandLineRunner在調用方法
在run方法裏面就完成完成整個Spring容器啓動流程了,包括Spring Cloud加載也是這裏完成的。下面詳細分析prepareEnvironment()
,配置文件上下文如何初始化的
prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 根據webApplicationType 創建
// SERVLET => ApplicationServletEnvironment
//REACTIVE=> ApplicationReactiveWebEnvironment
// NONE => ApplicationEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 命令行可能會有profile,可以選擇那個profile,也會將命令行參數生成一個PropertySources
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 添加 configurationProperties PropertySource到propertySourceList 隊列最前面
ConfigurationPropertySources.attach(environment);
// 執行所有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.");
// 從配置文件對應spring.main.* 屬性注入
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
//將類型轉換器設置到environment
environment = convertEnvironment(environment);
}
// 因爲EnvironmentPostProcessor 可能加載到配置文件裏,這時需要configurationProperties 放入第一
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment() 如果當前environment如何爲空,則會根據根據webApplicationType 類型選擇對應類進行初始化。大家可能好奇environment怎麼可能有值呢,接着玩下看,當我分析Spring Cloud時你就會返回environment不需要創建了。
ps: Environment 內部使用PropertySource區分不同配置文件,每一個源配置都有自己的名字,比如系統變量systemProperties、環境變量systemEnvironment等等。使用一個propertySourceList一個list將所有PropertySource保存起來,在隊列前面永遠最優先加載。
在上面寫過一個監聽器EnvironmentPostProcessorApplicationListener
,它處理environmentPrepared事件,使用SpringFactoriesLoader加載所有EnvironmentPostProcessor 前置處理器,其中之一ConfigDataEnvironmentPostProcessor
就是去做讀取配置文件,裏面還有很多邏輯處理,這裏就不展開了,有興趣的同學自行去分析代碼。讀取文件本身也是根據環境變量來的,這裏有幾個Spring內置配置
- spring.config.location 設定加載文件路徑,沒有則是使用類路徑./、config/
- spring.config.additional-location: 加載外部文件路徑,這個可以spring.config.location 共存,優先級最大
- spring.config.name 設定文件名前置,默認 application
上面這些變量都是從環境變量、系統變量中獲取的,當然不會從配置文件讀取到。通過設定文件路徑、文件名這樣方式確定加載文件,加載文件規則如下
{spring.config.name}-{extension}
- extension:文件名後綴 內置支持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);
//執行上面從SpringFactoriesLoader加載 ApplicationContextInitializer 對ConfigurableApplicationContext 屬性設置
applyInitializers(context);
// 調用SpringApplicationRunListener.contextPrepared 事件
listeners.contextPrepared(context);
// 執行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);
}
// 這裏已經從配置文件加載 設置到自身屬性上了,這時設置給上下文對象
// 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]));
// 所有監聽器執行contextLoaded 事件
listeners.contextLoaded(context);
}
在這裏完成了Spring 容器初始化,下一步就是啓動了。
bean初始化
其實我一直很好奇@Configuration這個注入如何實現配置類,還有還麼多Class要被Spring進行初始化,如何變成BeanDefinition最後變成bean。我確定從AbstractApplicationContext.refresh()
debug,終於被我發現Spring魔法,在invokeBeanFactoryPostProcessors()
在執行invokeBeanFactoryPostProcessors方法中回去獲取BeanDefinitionRegistryPostProcessor
類型內置對象,並且執行所有實現類。
-
BeanDefinitionRegistryPostProcessor
: 你可以理解成BeanDefinition註冊前置處理器,主要就是生成BeanDefinition,再還給容器。在Spring還沒有初始化bean時,這個接口運行實現類去初始化BeanDefinition再交還給Spring工廠對象,簡白點就是這個對象會創建BeanDefinition,交給Spring,後續進行初始化bean。下面要講解其中一個實現類ConfigurationClassPostProcessor
postProcessBeanFactory創建postProcessBeanFactory
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) { //這個方法只能執行一次,通過記錄上下文id標記執行
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 函數
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去做了,解析後的結果會變成 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 { //這裏是一個循環
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();
//已經將所有配置類全部解析出來 變成ConfigurationClass
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed); // 刪除已經解析過
// 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 轉化BeanDefinition ,並註冊到容器中
alreadyParsed.addAll(configClasses); //添加已經註冊過的,上面刪除對應
processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
candidates.clear();
// 當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 已經註冊到容器中,但是不是在ConfigurationClassParser 解析出來的結果,則說明bd並沒有通過解析生成
// 可能爲第三方 BeanDefinitionRegistryPostProcessor 生成BeanDefinition,加入到candidates 再次進入循環中
//被ConfigurationClassParser 解析,可以生成更多BeanDefinition
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty()); // 當所有BeanDefinition 都已經被解析完了,循環就可以退出了
//下面省略
}
看完上面的代碼,ConfigurationClassPostProcessor就是Spring將帶有@Configuration 標記Class經過一系列處理生成BeanDefinition的機制。在@SpringBootApplication 中有個一個@EnableAutoConfiguration帶有@Import(AutoConfigurationImportSelector.class),這個會被ConfigurationClassPostProcessor解析加載。其中AutoConfigurationImportSelector使用SpringFactoriesLoader加載,會將所有@EnableAutoConfiguration的配置類全部都加載ClassName,可以讓Spring Boot 加載ScanPackage 基礎包路徑之外的配置類,再通過@ConditionalOnBean、@ConditionalOnProperty這類註解,根據Class、配置判斷是否進行解析。
也就是說Spring Boot一開始就已經獲取到所有配置類,只有當符合條件時纔會進入解析、加載、實例化。
Spring Cloud
上面說了Spring Boot自動化配置接下來就是Spring Cloud方面,看了上面源碼,發現沒有代碼有關Spring Cloud,現在還不知道配置中心的配置如何作用到已經開始運行Spring 容器中。在開始分析代碼之前,先簡單看一個例子
可以看到applicatioinContext 有一個父級上下文,而這個就是Spring Cloud 上下文對象。看到這個是不是很驚奇,這個父級上下文在哪裏初始化的呢,從代碼角度去看了。
上面分析過ApplicationListener監聽器中,在Spring Cloud lib jar中有一個實現類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 這個配置是用來兼容舊版本配置文件加載
//我這裏環境引入spring-cloud-starter-bootstrap 第一個條件返回true,第二條件不用判斷
if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
return;
}
// don't listen to events in a bootstrap context
// 判斷environment 是否已經存在bootstrap 文件,已經加載過不需要往下執行了
//當父級初始化也會執行監聽器事件,到時來到這裏時,父級監聽器不會往下執行了
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
// 默認配置文件名,沒有在環境變量配置默認就是bootstrap
String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) { //在已經存在ParentContextApplicationContextInitializer 中返回父級容器
context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
}
}
if (context == null) { //當上面ParentContextApplicationContextInitializer 沒有執行就會走下面初始化父級容器方法
// 這裏會返回父級容器,也就是Spring Cloud 上下文對象
context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
}
//從父級容器中獲取ApplicationContextInitializer 交給SpringApplication
//父級生成ApplicationContextInitializer 用於增強子類
apply(context, event.getSpringApplication(), environment);
}
這個監聽器主要根據配置文件信息來啓動Spring Cloud組件,如果沒有相應的配置根據項目環境來,看下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加載文件的配置信息,規則類似上面加載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來創建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);
// 這裏將調用SpringApplication.run 上面已經分析,
final ConfigurableApplicationContext context = builder.run();
context.setId("bootstrap");
//這裏添加AncestorInitializer 是一個ApplicationContextInitializer 實現類,目的就是讓子applicationContext 和父級關聯起來
addAncestorInitializer(application, context);
//當前environment 爲子集配置對象,這裏要刪除掉父級加載文件信息
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
//將 springCloudDefaultProperties 配置文件信息copy到environment 中
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
現在所有代碼都看完了,我們來理一理整一個流程就會清晰明瞭。
在BootstrapApplicationListener中會根據配置文件或者是項目環境jar來是否啓動加載bootstrap配置文件。先從生成加載Spring Cloud配置信息,
使用SpringApplicationBuilder來構建SprAppingApplication對象,然後執行SpringApplication.run 方法,這個代碼我們已經分析過了,初始化Spring容器上下文對象,然後進入核心refresh方法執行IOC。SpringApplicationBuilder構造SpringApplication 中沒有像我們寫啓動類main方法,會設置啓動類Class。所以被ConfigurationClassPostProcessor
解析BeanDefinition,並沒有@SpringApplication 這個註解,所以這個Spring Cloud 工廠沒有獲取到basepackae、@EnableAutoConfiguration這些東西。根據上面代碼知道Spring Cloud將BootstrapImportSelectorConfiguration
作爲BeanDefinition交給ConfigurationClassPostProcessor
,這樣父級容器只有加載BootstrapConfiguration
標記類,父級bean和子級bean相互隔離。這樣父級容器就可以去啓動與Spring Cloud有關的bean。當Spring Cloud容器已經完成bean初始化後,再來執行SpringApplicaton.run 啓動Spring 容器創建。這樣在子級啓動之前已經將配置中心的配置對應的對象已經創建出來了。再通過ApplicationContextInitializer接口將配置對象加載ConfigurableEnvironment中。
這裏使用較短的篇幅來分析Spring Boot這個框架如何工作,站在自己的思維上,使用3個知識點來展示Spring Boot技術細節實現。第一個從SpringApplication.run
瞭解Spring兩大工廠對象ConfigurableApplicationContext
、ConfigurableEnvironment
如何初始化處理出來的,配置文件如何被加載的,加載規則,知識點SpringFactoriesLoader機制,如果要做Spring Boot組件必須要這個。瞭解了Spring Boot ApplicationContextInitializer、ApplicationListener這些接口,還有SpringApplicationRunListener
爲整個Spring Boot事件監聽器,對應整個框架的不同階段處理。第二簡單分析了
Spring容器啓動時如何生成BeanDefinition的機制實現類:BeanDefinitionRegistryPostProcessor
,瞭解了Spring Boot組件如何被加載、實例化,這個依賴啓動類的註解。最後Spring Cloud組件如何加載實例化,這個依賴於前面兩個。