1.简介
Spring Cloud比Spring Boot多了一个Bootstrap Context的上下文,对照源码看下Bootstrap Context是怎么初始化的。
2.启动方式
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableDiscoveryClient
开启服务发现
3.启动
-
SpringApplication#run
准备创建environment
,然后执行创建监听事件。
-
SpringApplication#prepareEnvironment
这里可以看到有一个事件监听类EventPublishingRunListener
,注意这里的监听类是实现的SpringApplicationRunListener
接口。
这个类主要是获取SpringApplication
中的所有ApplicationListener
,然后传播事件。代码如下:
我们刚才的事件是environmentPrepared
,找EventPublishingRunListener
对应的代码:
从SpringApplication中获取到的ApplicationListener
中有一个BootstrapApplicationListener
,这里是Spring Cloud初始化的开始。
-
BootstrapApplicationListener#onApplicationEvent
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// 获取当前时间的环境
ConfigurableEnvironment environment = event.getEnvironment();
// 不允许初始化Spirng Cloud Bootstrap
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// don't listen to events in a bootstrap context
// 初始化一次后会添加一个名称为BOOTSTRAP_PROPERTY_SOURCE_NAME的PropertySource,用来判断是否已经初始化过
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
//获取bootstrap配置文件的名称 默认:bootstrap
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
//尝试获取当前已经存在的Parent Context (这里一般不会进来)
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
if (context == null) {
// 创建一个Application Context
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
// 添加一个Close监听事件
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
// 将Bootstrap Application Context中加载的信息合并到当前的application和environment中
apply(context, event.getSpringApplication(), environment);
}
-
BootstrapApplicationListener#bootstrapServiceContext
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
// 创建一个新的environment
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
//清空PropertySource
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
//获取bootstrap配置文件地址
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
String configAdditionalLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
// configName为bootstrap (未配置的情况下)
bootstrapMap.put("spring.config.name", configName);
// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
// will fail
// force the environment to use none, because if though it is set below in the
// builder
// the environment overrides it
// 设置WebApplication类型 这里因为这个Application主要是加载配置 所以设置为none
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);
}
//
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 注意environment
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) {
// gh_425:
// SpringApplication cannot deduce the MainApplicationClass here
// if it is booted from SpringBootServletInitializer due to the
// absense of the "main" method in stackTraces.
// But luckily this method's second parameter "application" here
// carries the real MainApplicationClass which has been explicitly
// set by SpringBootServletInitializer itself already.
builder.main(application.getMainApplicationClass());
}
// 判断是否刷新参数
if (environment.getPropertySources().contains("refreshArgs")) {
// If we are doing a context refresh, really we only want to refresh the
// Environment, and there are some toxic listeners (like the
// LoggingApplicationListener) that affect global static state, so we need a
// way to switch those off.
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
// 设置需要加载的source类
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
// gh-214 using spring.application.name=bootstrap to set the context id via
// `ContextIdApplicationContextInitializer` prevents apps from getting the actual
// spring.application.name
// during the bootstrap phase.
// 设置context名称为bootstrap
context.setId("bootstrap");
// Make the bootstrap context a parent of the app context
//设置Parent Context
addAncestorInitializer(application, context);
// It only has properties in it now that we don't want in the parent so remove
// it (and it will be added back later)
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
// 合并配置
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
Bootstrap Context创建完成后会context与当前的application、environment进行处理
-
BootstrapApplicationListener#apply
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
// 判断是否已经处理过
if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
return;
}
// 添加标记类
application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
@SuppressWarnings("rawtypes")
Set target = new LinkedHashSet<>(application.getInitializers());
// 添加Bootstrap Context中加载的ApplicationContextInitializer Bean
target.addAll(
getOrderedBeansOfType(context, ApplicationContextInitializer.class));
application.setInitializers(target);
// 添加解密的`Initializer`
addBootstrapDecryptInitializer(application);
}
至此整个Bootstrap Context
初始化完成,后面的逻辑和普通的Spring Boot
流程基本一致。
4.结束
今天大致的写了下Bootstrap Context
的初始化逻辑,下一篇文章讲深入细节,记录下配置文件的加载逻辑。
- bootstrap.yaml加载
- 从配置中心加载配置
- application.yaml加载
- 其他配置加载