提前说明:关于Spring Cloud和Spring Boot源码分析基于的版本如下所示
<!-- Spring Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.8.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Spring Cloud
项目是基于Spring Boot
项目的,我们创建的Spring Cloud
项目其实包含了两个Spring
容器,一个是Spring Cloud
的,一个是Spring Boot
的,Spring Cloud
作为父容器。这两个容器都是分开进行实例化的,最后关联起来。一开始是Spring Boot
项目启动,然后在环境准备阶段会进入到BootstrapApplicationListener
这个监听器当中,通过这个监听器会创建一个属于Spring Cloud
的SpringApplication
对象(与Spring Boot
创建异曲同工,只不过有一些自己特别的配置而已),执行SpringApplication
对象的run
方法就会创建一个Spring Cloud
的容器对象。
这个阶段完成之后,Spring Boot的容器还没有创建,当Spring Boot容器创建完成之后,会执行初始化操作(主要就是一系列的初始化器),通过这个初始化器,最终完成两个容器的融合(设置父子关系和合并环境参数操作)。
因此bootstrap.properties
这个文件的读取其实是分为两个阶段的,一个是在Spring Cloud
这个容器创建过程中读取文件的过程(创建Spring Cloud容器阶段),一个是在Spring Boot
容器初始化过程中环境参数配置的融合过程(设置父子容器阶段)。后续Spring Boot
就可以获取Spring Could
的Bean以及相关配置了。
创建Spring Cloud容器
org.springframework.cloud.bootstrap.BootstrapApplicationListener
监听事件 读取配置
- 可以通过设置
spring.cloud.bootstrap.enabled
为false,不读取bootstrap配置,但是此配置参数必须在当前监听器之前。(默认情况下此配置为true) - 已经解析过(已经存在名称为
bootstrap
的MutablePropertySources
对象)或者当前正在解析,不需要再进行解析
@Override
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;
}
- 解析configName,通过读取环境配置属性
spring.cloud.bootstrap.name
,默认值为bootstrap
.
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
- 构建ConfigurableApplicationContext对象(
BootstrapContext
)
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
if (context == null) {
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
默认情况下,存在以下ApplicationContextInitializer
对象(通过SPI机制读取,在SpringApplication对象构造过程中初始化的)
以上实例中没有一个是ParentContextApplicationContextInitializer
类型,因此会进入bootstrapServiceContext
方法。
bootstrapServiceContext创建SpringCloud上下文
首先创建一个StandardEnvironment对象,并移除内部的PropertySource
列表信息,也就是一个空的配置对象。
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());
}
读取bootstrap文件的路径,默认为"",也就是当前classpath
配置相关的参数
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);
}
创建名称为bootstrap
的MapPropertySource
类型资源对象
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
通过构造者模式创建一个普通类型WebApplicationType==NONE
的SpringApplication
对象
// TODO: is it possible or sensible to share a ResourceLoader?
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());
}
// 默认不包含 因此不会执行context refresh
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()));
}
添加主类资源BootstrapImportSelectorConfiguration
启动bootstrap对应的springapplication
对象
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
}
以上的逻辑与spring boot项目基本相似,只是在再次执行BootstrapApplicationListener
时,因为配置资源列表中包含名称为 bootstrap
的资源,因为不会再进入以上的逻辑(否则要循环无穷无尽了…)。第二个区别在于,ConfigFileApplicationListener
本来是用于读取Spring Boot的默认配置文件的。可以参考博客:
SpringBoot是如何加载application.properties的:https://blog.csdn.net/m0_37607945/article/details/106577833
但是此处用于读取了SpringCloud的配置文件.从以下debug步骤可以看出:
主要的原因在于以上的资源列表中包含了一个名称为bootstrap
的资源,并且设置了属性spring.config.name
为bootstrap
,因此此时不会读取默认的名称application
,而是查找bootstrap
.当然默认的路径也一致。由于此处加载的逻辑与application.properties的逻辑是一致的,不进行额外的探讨了。
最后还要注意,这个在里面创建的SpringAppliation
对象类型为org.springframework.context.annotation.AnnotationConfigApplicationContext
,非web类型,因此不会启动一个web服务器。
另外在prepareContext
中,sources
也不是当前项目的启动类,而是前面传入的BootstrapImportSelectorConfiguration
类,这个类的定义如下:
@Configuration
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {
}
很明显(@Import
)会导致引入BootstrapImportSelector
类型的bean.此逻辑是通过ConfigurationClassPostProcessor
这个后置处理器来实现的。具体参考博客:
ConfigurationClassPostProcessor:https://blog.csdn.net/m0_37607945/article/details/106676299
BootstrapImportSelector
用于通过SpringFactoriesLoader
读取spring.factories
文件中key为BootstrapConfiguration
的资源。另外这个类也实现了DeferredImportSelector
接口,因此会在所有注解@Configuration
的类处理完之后才会进行处理。配合@Conditional
注解使用更高效。
BootstrapImportSelector的import过程
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 进行初始化
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// 注入Aware信息
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
// 进行处理 其实只是添加到一个列表deferredImportSelectors中
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
在org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
最后再进行deferredImportSelectorHandler
的处理。
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
最后processGroupImports
中会调到org.springframework.cloud.bootstrap.BootstrapImportSelector#selectImports
方法
从spring.factories
文件中读取BootstrapConfiguration
配置信息,在路径下搜索org.springframework.cloud.bootstrap.BootstrapConfiguration
又Spring Cloud提供的如下
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = new ArrayList<>(SpringFactoriesLoader
.loadFactoryNames(BootstrapConfiguration.class, classLoader));
进行排序按照Order注解并返回这些类的类名称数组
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;
}
获取到需要进行注册的类名称之后
org.springframework.context.annotation.ConfigurationClassParser.DefaultDeferredImportSelectorGroup#process
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
for (String importClassName : selector.selectImports(metadata)) {
this.imports.add(new Entry(metadata, importClassName));
}
}
最后在org.springframework.context.annotation.ConfigurationClassParser#processImports中进行处理
通过注册了不少bean,如下所示:
引入的关系图以及类的作用:
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration的作用
PropertySourceBootstrapConfiguration
主要用于读取SpringCloud的配置属性,比如spring.cloud.config.override-system-properties
,另外也开启了EnableConfigurationProperties
的功能。
@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered
->PropertySourceBootstrapProperties
(读取spring.cloud.config配置)、
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
/**
* Convenient way to quickly register {@link ConfigurationProperties} annotated beans
* with Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@link ConfigurationProperties} annotated beans to register
*/
Class<?>[] value() default {};
}
EnableConfigurationPropertiesImportSelector
(EnableConfigurationProperties注解)
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}
}
->(注入了两个类)ConfigurationPropertiesBeanRegistrar
(收集EnableConfigurationProperties
注解中的类并注册到容器中)、ConfigurationPropertiesBindingPostProcessorRegistrar
(注入ConfigurationPropertiesBindingPostProcessor
和ConfigurationBeanFactoryMetadata
)->ConfigurationPropertiesBindingPostProcessor
(将配置属性设置到注解了ConfigurationProperties的bean中的后置处理器)、ConfigurationBeanFactoryMetadata
(元数据工具类)
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration的作用(属性加密)
EncryptionBootstrapConfiguration
->environmentDecryptApplicationListener
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration的作用(注册ConfigurationPropertiesRebinder
,监听EnvironmentChangeEvent
事件然后刷新与ConfigurationProperties
相关的属性配置,可参考RefreshScope
)
ConfigurationPropertiesRebinderAutoConfiguration
->configurationPropertiesBeans
(收集包含ConfigurationProperties
注解的bean的引用)、configurationPropertiesRebinder
private ConfigurationPropertiesBeans beans;
public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
this.beans = beans;
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
rebind();
}
}
@ManagedOperation
public void rebind() {
this.errors.clear();
// 遍历收集到的引用
for (String name : this.beans.getBeanNames()) {
// 进行再次绑定 更新bean的信息
rebind(name);
}
}
@ManagedOperation
public boolean rebind(String name) {
if (!this.beans.getBeanNames().contains(name)) {
return false;
}
if (this.applicationContext != null) {
try {
Object bean = this.applicationContext.getBean(name);
if (AopUtils.isAopProxy(bean)) {
bean = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
this.applicationContext.getAutowireCapableBeanFactory()
.destroyBean(bean);
this.applicationContext.getAutowireCapableBeanFactory()
.initializeBean(bean, name);
return true;
}
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
catch (Exception e) {
this.errors.put(name, e);
throw new IllegalStateException("Cannot rebind to " + name, e);
}
}
return false;
}
PropertyPlaceholderAutoConfiguration
->PropertySourcesPlaceholderConfigurer
(解析占位符,非Spring Cloud特有 此处不探讨)
经过bean的注册、实例化完整容器的refresh,然后将容器名称设置为bootstrap
。并且添加AncestorInitializer
(管理父容器,也就是bootstrap)。
// 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.setId("bootstrap");
// Make the bootstrap context a parent of the app 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;
apply
apply(context, event.getSpringApplication(), environment);
}
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
@SuppressWarnings("rawtypes")
// 从bootstrap容器中获取ApplicationContextInitializer
List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
ApplicationContextInitializer.class);
// 添加到Spring Boot中
application.addInitializers(initializers
.toArray(new ApplicationContextInitializer[initializers.size()]));
addBootstrapDecryptInitializer(application);
}
private void addBootstrapDecryptInitializer(SpringApplication application) {
DelegatingEnvironmentDecryptApplicationInitializer decrypter = null;
for (ApplicationContextInitializer<?> ini : application.getInitializers()) {
if (ini instanceof EnvironmentDecryptApplicationInitializer) {
@SuppressWarnings("unchecked")
ApplicationContextInitializer del = (ApplicationContextInitializer) ini;
decrypter = new DelegatingEnvironmentDecryptApplicationInitializer(del);
}
}
if (decrypter != null) {
application.addInitializers(decrypter);
}
}
设置父子容器关系
当Spring Boot的容器创建之后,会进入到prepareContext阶段,在此阶段,将会进行容器对象的初始化阶段(创建过程称为实例化阶段),applyInitializers(context)
。
添加Spring Cloud容器的配置信息到Spring Boot容器当中
org.springframework.boot.builder.ParentContextApplicationContextInitializer#initialize
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
if (applicationContext != this.parent) {
applicationContext.setParent(this.parent);
applicationContext.addApplicationListener(EventPublisher.INSTANCE);
}
}
设置容器间的关系
org.springframework.context.support.AbstractApplicationContext#setParent
/**
* Set the parent of this application context.
* <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is
* {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
* this (child) application context environment if the parent is non-{@code null} and
* its environment is an instance of {@link ConfigurableEnvironment}.
* @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
*/
@Override
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
设置beanFactory关系
org.springframework.context.support.GenericApplicationContext#setParent
/**
* Set the parent of this application context, also setting
* the parent of the internal BeanFactory accordingly.
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#setParentBeanFactory
*/
@Override
public void setParent(@Nullable ApplicationContext parent) {
super.setParent(parent);
this.beanFactory.setParentBeanFactory(getInternalParentBeanFactory());
}
添加一个父容器的监听器(同时也是事件发布器org.springframework.boot.builder.ParentContextApplicationContextInitializer.EventPublisher
),在监听到子容器发布的ContextRefreshedEvent
事件之后再发布ParentContextAvailableEvent
事件
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableApplicationContext && context == event.getSource()) {
context.publishEvent(new ParentContextAvailableEvent((ConfigurableApplicationContext) context));
}
}
总结:Spring Boot的流程