文章目录
一. 前言
1. 相关文章
- SpringBoot启动源码浅析
- SpringCloud父子容器源码浅析 (本文)
2. 说明
- 本文SpringCloud源码解读使用的版本为2.2.7.RELEASE. 引入的依赖为 spring-cloud-context:2.2.7.RELEASE
- 本文涉及的SpringBoot源码版本为2.3.9.RELEASE. 引入依赖 spring-boot-starter-web:2.3.9.RELEASE
- 附上SpringCloud官方文档中文版地址: https://www.springcloud.cc/spring-reference.html
- 附上SpringCloud官方文档地址: https://spring.p2hp.com/cloud.html
二. 源码浅析
1. 启动流程
简易流程
2. 进入源码
2.1 父容器创建入口
SpringApplication#prepareEnvironment, 准备环境完成后, 发布ApplicationEnvironmentPreparedEvent事件
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//...
// 准备环境完成, 发布ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(environment);
//...
}
2.2 BootstrapApplicationListener监听事件处理
BootstrapApplicationListener这个监听器是在spring-cloud-context的META-INF.spring.factories中添加的. 在SpringApplication实例化时该bean就会添加到应用监听器列表中去
进入BootstrapApplicationListener#onApplicationEvent
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
// 是否开启bootstrap父容器创建, 默认都是开启的
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
return;
}
// 判断enviroment中是否有"bootstrap"配置源. 有该配置源则表示正在创建父容器, 直接结束避免死循环
if (environment.getPropertySources().contains("bootstrap")) {
return;
}
ConfigurableApplicationContext context = null;
// 通过ParentContextApplicationContextInitializer获取父容器.
String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
// 获取ParentContextApplicationContextInitializer中存储的父容器
// 注: 正常启动时, debug发现这里永远不会执行, 因为ParentContextApplicationContextInitializer是手动new出来没有添加到容器中.
// 源码位置BootstrapApplicationListener.AncestorInitializer#initialize
context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
}
}
if (context == null) {
// 创建父容器, 添加祖先监听器, 为Environment添加属性源
context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
// 为子SpringApplication添加一个应用监听器CloseContextOnFailureApplicationListener, 监听启动失败事件
event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
}
// 为子SpringApplication添加主加载源和添加应用上下文监听器
apply(context, event.getSpringApplication(), environment);
}
2.3 创建父容器(核心代码)
核心的代码在BootstrapApplicationListener#bootstrapServiceContext中. 下面展开看下它做了什么
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
final SpringApplication application, String configName) {
// 创建父容器的Environment(实现类为StandardEnvironment), 清空所有属性源PropertySource
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
// 构建一个名为bootstrap的属性源, 添加到父容器的Environment
String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
String configAdditionalLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
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);
}
bootstrapProperties.addFirst(new MapPropertySource("bootstrap", bootstrapMap));
// 父Environment添加 子Environment中非StubPropertySource的属性源
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
// 构建父SpringApplication
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
// 正常启动不执行代码块. main方法启动类是SpringApplication创建时可以从堆栈中获取到的
if (builderApplication.getMainApplicationClass() == null) {
builder.main(application.getMainApplicationClass());
}
// 正常启动不执行代码块. 若子environment含有refreshArgs属性源则过滤掉日志相关的监听器
if (environment.getPropertySources().contains("refreshArgs")) {
builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
}
// 添加加载源, 在build()时会将该加载源作为父SpringApplication唯一的primarySources
builder.sources(BootstrapImportSelectorConfiguration.class);
// 执行父SpringApplication的run方法, 获取到父容器ConfigurableApplicationContext
final ConfigurableApplicationContext context = builder.run();
// 父容器命名为bootstrap
context.setId("bootstrap");
// 给子SpringApplication添加一个祖先初始化器AncestorInitializer.
// AncestorInitializer作用: 暂存了父容器对象, 待后续创建出子容器后进行父子容器绑定
addAncestorInitializer(application, context);
// 移除父容器environment中的bootstrap的属性源
bootstrapProperties.remove("bootstrap");
// 为父子environment都添加同一个名为springCloudDefaultProperties空的属性源
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
这里实际上就做了以下几件事
- 创建父Environment和父SpringApplication, 执行run方法后创建出父容器
- 为子SpringApplication添加祖先监听器, 以便后续创建出子容器后 完成父子容器绑定
- 为父子SpringApplication添加一个公共的名为springCloudDefaultProperties空的属性源
这里提到了一个祖先监听器AncestorInitializer, 我们简单看下它的代码
private static class AncestorInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private ConfigurableApplicationContext parent;
AncestorInitializer(ConfigurableApplicationContext parent) {
this.parent = parent;
}
//...
public void initialize(ConfigurableApplicationContext context) {
while (context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext) context.getParent();
}
// 重排子Environment中springCloudDefaultProperties属性源顺序
reorderSources(context.getEnvironment());
// 绑定父子容器关系. 等价于context.setParent(this.parent)
new ParentContextApplicationContextInitializer(this.parent)
.initialize(context);
}
//...
}
// ParentContextApplicationContextInitializer#initialize代码如下
private final ApplicationContext parent;
public void initialize(ConfigurableApplicationContext applicationContext) {
if (applicationContext != this.parent) {
// 绑定父子容器
applicationContext.setParent(this.parent);
// 为子容器添加EventPublisher监听器
applicationContext.addApplicationListener(EventPublisher.INSTANCE);
}
}
AncestorInitializer监听器是加到子SpringApplication中, 当子容器创建完成调用SpringApplication#prepareContext方法时, 就会执行到该祖先初始化器的initialize方法, 最终执行到ParentContextApplicationContextInitializer#initialize, 完成父子容器的绑定
注: 这里的ParentContextApplicationContextInitializer仅仅是为了调用初始化方法, 并没有将该初始化器的bean放入容器中
2.4 执行BootstrapApplicationListener#apply
为子SpringApplication添加主加载源和初始化器
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
return;
}
// 为子SpringApplication添加一个主加载源BootstrapMarkerConfiguration
application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
// 为子SpringApplication添加所有类型为ApplicationContextInitializer的初始化器
Set target = new LinkedHashSet<>(application.getInitializers());
target.addAll(getOrderedBeansOfType(context, ApplicationContextInitializer.class));
application.setInitializers(target);
// 为子SpringApplication添加初始化器DelegatingEnvironmentDecryptApplicationInitializer
addBootstrapDecryptInitializer(application);
}
private void addBootstrapDecryptInitializer(SpringApplication application) {
DelegatingEnvironmentDecryptApplicationInitializer decrypter = null;
Set<ApplicationContextInitializer<?>> initializers = new LinkedHashSet<>();
for (ApplicationContextInitializer<?> ini : application.getInitializers()) {
if (ini instanceof EnvironmentDecryptApplicationInitializer) {
ApplicationContextInitializer del = ini;
decrypter = new DelegatingEnvironmentDecryptApplicationInitializer(del);
initializers.add(ini);
// 若子SpringApplication含有EnvironmentDecryptApplicationInitializer初始化器,
// 则再添加一个对应的DelegatingEnvironmentDecryptApplicationInitializer
initializers.add(decrypter);
} else if (ini instanceof DelegatingEnvironmentDecryptApplicationInitializer) {
// do nothing
} else {
initializers.add(ini);
}
}
ArrayList<ApplicationContextInitializer<?>> target = new ArrayList<ApplicationContextInitializer<?>>(
initializers);
application.setInitializers(target);
}
三. 拓展
暂无
四. 思考
1. 为什么SpringCloud使用父子容器这种模式? 父子容器的作用是什么?
以下仅为个人理解
- SpringBoot创建的容器(IOC容器)不适合SpringCloud框架代码的执行.
- SpringCloud容器和SpringBoot容器相互隔离, 以免父容器启动时修改到子容器的关键属性. 例如两者所需的Environment和PropertySource是不一样的, SpringCloud加载配置文件和SpringBoot也不完全相同
- SpringCloud和SpringBoot启动所需条件是不一样的. 例如SpringCloud以非servlet和非reactive的应用类型启动.
2. 使用nacos配置中心时, 若启动完成后, 去更改配置文件, 会重新触发Spring启动, 那此时父子容器创建和启动时有什么不同呢?
五. 参考文档
- 聊聊SpringCloud环境下父子容器: https://blog.csdn.net/jackcheng1117/article/details/105844597