1.前言
接触过Spring Cloud都知道,服务启动的时候会先启动Spring Cloud容器加载bootstrap.yml的配置,然后再启动我们常说的Spring容器,那么为什么需要父子容器,父容器又是在什么地方进行创建的呢?
2. 为什么需要父子容器?
这个问题暂时没看到官方的答案,但可以根据父子容器的目的来进行推论一下。
- 隔离 Spring Cloud目的是为了解决微服务之间通信时候的一系列问题,它本质并不与具体的业务有关系,所以将它设置为父容器,我们可以将环境独立,不与具体业务环境(Spring容器)进行耦合。
- 层级划分 Spring Cloud内的Bean无法引用Spring容器中的Bean,做到容器父子层级划分,避免互相引用的问题。
- 业务需求 我们知道远程配置是通过PropertySourceBootstrapConfiguration的初始化执行时处理的,但这个时候我们的bean还未创建,所以无法直接使用。那么这个时候就需要在父容器中提前创建好某些远程配置的服务,满足我们业务需求
如果有其它的原因或者原因不对,也希望大家能留言指出~
3. 父容器创建
Spring Cloud容器的创建是在Spring容器准备阶段创建的,具体我们看到Spring容器启动阶段。
1.prepareEnvironment准备环境
进入到Spring Application的run方法,他是在准备容器阶段prepareEnvironment进行创建的。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
2. SpringApplicationRunListeners监听器列表包装类
void environmentPrepared(ConfigurableEnvironment environment) {
// 循环调用所有的listener
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
3.EventPublishingRunListener事件发布监听器
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
// 发布环境准备好的时间,
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
这里有点绕,但本质就是EventPublishingRunListener这个SpringBoot的监听器会帮忙发布一个事件ApplicationEnvironmentPreparedEvent,然后给对应注册的Spring监听器来处理。
4.SimpleApplicationEventMulticaster发布事件
Spring的事件发布,通过一个multicaster来进行的,会在multicaster里面保存所有的listeners,然后进行调用。
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 拿到监听该事件的listener进行invoke调用。同时判断是否需要异步调用,默认都是同步处理
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
Spring Cloud启动时候,默认会有一些监听器,我们找到BoostrapApplicationListener,这个就是我们关键需要用到的Listeners。
5.BootstrapApplicationListener启动监听器
核心关键就是他的onApplicationEvent方法了
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
// 如果没bootstrap启用
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// 如果是bootstrap容器则跳过
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
if (context == null) {
// 创建父容器
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
// 子容器增加一个关闭父容器的监听器,在关闭Spring容器的时候,也能关闭父容器
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
// 将父容器的initializer添加到Spring容器中
apply(context, event.getSpringApplication(), environment);
}
我们发现关键在创建父容器的bootstrapServiceContext方法中。
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
// 创建父容器的环境,参数里面的environment是Spring容器的
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
// 创建时候会默认添加两个配置文件systemEnvironment、systemProperties
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
// 删掉默认的两个系统配置
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
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);
// 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
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);
}
// 首先添加boostrap的配置文件
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
// 添加非stubPropertySources(servlet的都是stub类型),这里拿的是Spring容器的环境配置,前面删除的是父容器创建的环境配置。
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
// 创建容器,这里创建的就是Spring Cloud的容器,环境用的是bootstrapEnvironment
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
.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()));
}
builder.sources(BootstrapImportSelectorConfiguration.class);
// 启动Spring Cloud容器,从SpringApplication开始重新执行一次启动流程
final ConfigurableApplicationContext context = builder.run();
context.setId("bootstrap");
// 增加祖先初始化器,如果已经存在就把子容器里面的祖先初始化器设置上父容器,否则创建新的
// 这个初始化器会在子容器初始化时候设置上parent会当前Spring Cloud容器
addAncestorInitializer(application, context);
// 将bootstrap配置移除掉,因为下面要合并配置到子容器里面
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
6.onApplicationEvent最后的apply方法
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// ...省略前面
apply(context, event.getSpringApplication(), environment);
}
前面的监听执行的时候会有一句apply方法执行。
@SuppressWarnings("unchecked")
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
return;
}
// 增加BootstrapMarkerConfiguration到资源中,上一行代码判断已经存在就会直接return,但具体什么情况会发生暂时不太清楚,因为在前面子容器执行就已经return了。
application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
// 拿到Spring容器中的初始化器
@SuppressWarnings("rawtypes")
Set target = new LinkedHashSet<>(application.getInitializers());
// 把父容器的初始化器添加进去
target.addAll(
getOrderedBeansOfType(context, ApplicationContextInitializer.class));
application.setInitializers(target);
// 增加bootstrapdecrpyt初始化器
addBootstrapDecryptInitializer(application);
}
本质就是把父容器的初始化器放到子容器中,后续启动时候能使用到。
4.疑问
1.BootstrapApplicationListener会不断创建父容器吗?
不会的,父子容器虽然走的启动流程都是一样的,都会发ApplicationEnvironmentPreparedEvent事件,但监听到后有做过滤,使得父容器不会再执行一次创建父容器的流程。具体在onApplicationEvent中,第二次调用时候会判断环境中是否有bootstrap的配置,如果有就会直接返回了。
// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
2. 父容器怎么加载bootstrap文件?
在BootstrapApplicationListener的bootstrapServiceContext方法执行时,创建父容器之前,往容器中放了一个配置类BootstrapImportSelectorConfiguration,这个类会在类型为beanfactoryPostProcessor的ConfigurationClassPostProcessor扫描配置中执行,和enableautoconfiguration实现同理,只是SPI读取的key不同。
5. 总结
Spring Cloud通过在容器启动时候的环境已准备的事件来进行父容器的创建,父容器的创建很好地隔离开Spring Cloud、Spring Boot应用的环境和配置。
评论