本文基于:spring-boot-2.3.12-RELEASE、spring-cloud-Hoxton.SR12(即2.2.9-RELEASE)
在看eureka server源码的时候发现spring的refresh方法被执行了两次!开始还以为是自己代码写错了,经过调试发现spring cloud竟然自己启动了一个单独的容器;下面我们来分析一下代码原理;
BootstrapApplicationListener引入时机
接上一篇讲解【springboot事件管理机制】文章 https://blog.youkuaiyun.com/Aqu415/article/details/126197702
spring cloud框架提供了的BootstrapApplicationListener会在SpringApplication的构造方法里被解析并缓存进入容器
类图如下:
onApplicationEvent 方法分析
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// don't listen to events in a bootstrap context
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) {
// @A
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
apply(context, event.getSpringApplication(), environment);
}
@A:spring cloud 另起炉灶,启动新的spring容器,确切的说是SpringApplication
bootstrapServiceContext方法
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
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:}");
... 省略...
// @A
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();
... 省略...
// @B
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
// @C
addAncestorInitializer(application, context);
... 省略...
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
@A:构建一个SpringApplicationBuilder对象
@B:设置springapplication的sources属性【偷天换日】复用springboot流程启动一个容器;下面会分析
@C:将spring cloud的容器和main方法启动的容器设置父子关系
private void addAncestorInitializer(SpringApplication application,
ConfigurableApplicationContext context) {
boolean installed = false;
... 省略...
if (!installed) {
// @A
application.addInitializers(new AncestorInitializer(context));
}
}
@A:向application的Initializer里放了一个AncestorInitializer对象,application即springboot原生的应用对象;在外层主流程的prepareContext会调用所有的Initializer的initialize方法
图中
1、@A是spring cloud的BootstrapApplicationListener执行时机
2、@B是所有Initializer执行时机
initialize 方法
@Override
public void initialize(ConfigurableApplicationContext context) {
while (context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext) context.getParent();
}
reorderSources(context.getEnvironment());
// @A
new ParentContextApplicationContextInitializer(this.parent)
.initialize(context);
}
@A:设置springcloud容器和标准springboot容器的父子关系
BootstrapImportSelectorConfiguration
上面讲了springcloud借助springboot的事件机制将BootstrapApplicationListener注入springboot容器,BootstrapApplicationListener将BootstrapImportSelectorConfiguration注入到springcloud容器;
BootstrapApplicationListener中使用的如下代码:
builder.sources(BootstrapImportSelectorConfiguration.class);
其源码如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Configuration to import the {@link BootstrapImportSelector} configuration.
*
* @author Spencer Gibb
*/
@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {
}
1、首先 BootstrapImportSelectorConfiguration 是一个被@Configuration标注的配置类,并且该类被@Import注解;
2、框架会继续解析 BootstrapImportSelector,解析的时机可以看我之前的文章 【Spring之ConfigurationClassPostProcessor配置类后置处理器源码分析】
BootstrapImportSelector
public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector {
...省略...
}
是一个延迟处理ImportSelector,我们来看看它的import方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
// @A
List<String> names = new ArrayList<>(SpringFactoriesLoader
.loadFactoryNames(BootstrapConfiguration.class, classLoader));
names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));
List<OrderedAnnotatedElement> elements = new ArrayList<>();
for (String name : names) {
try {
elements.add(
new OrderedAnnotatedElement(this.metadataReaderFactory, name));
}
catch (IOException e) {
continue;
}
}
AnnotationAwareOrderComparator.sort(elements);
String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);
return classNames;
}
这个方法主要功能是到spring.factories文件获取key为 org.springframework.cloud.bootstrap.BootstrapConfiguration的配置,即@A 处代码
我们看看一共加载了如下几个类
名称 | 主要功能 |
---|---|
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration | 处理springcloud的 bootstrap.properties 或者bootstrap.yml 配置文件 |
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration | 与属性读取的加解密有关,跟JDK的keystore也有一定的关联 |
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration | |
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration | 和spring常见的解析文件一样的操作 |
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration | |
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration | 用RestTemplate与eurekaserver建立连接 |
但是不是所有的类都会被解析,有的类在不满足条件(@Condition机制)的情况下会被抛弃:如上边的EurekaConfigServerBootstrapConfiguration会被抛弃,因为这是一个eureka client端的类;
小结
从目前代码分析来看,springcloud在个流程过程中启动容器最大的一个作用就是把配置文件加载;然后把自己的容器设置为springboot容器的父容器;
over~~