Spring Cloud项目是如何读取bootstrap.properties文件的?

提前说明:关于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 CloudSpringApplication对象(与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

在这里插入图片描述

监听事件 读取配置

  1. 可以通过设置spring.cloud.bootstrap.enabled为false,不读取bootstrap配置,但是此配置参数必须在当前监听器之前。(默认情况下此配置为true)
  2. 已经解析过(已经存在名称为bootstrapMutablePropertySources对象)或者当前正在解析,不需要再进行解析
@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;
	}
  1. 解析configName,通过读取环境配置属性spring.cloud.bootstrap.name,默认值为bootstrap.
	ConfigurableApplicationContext context = null;
	String configName = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
  1. 构建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);
	}

创建名称为bootstrapMapPropertySource类型资源对象

	bootstrapProperties.addFirst(
			new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
	for (PropertySource<?> source : environment.getPropertySources()) {
		if (source instanceof StubPropertySource) {
			continue;
		}
		bootstrapProperties.addLast(source);
	}

在这里插入图片描述
通过构造者模式创建一个普通类型WebApplicationType==NONESpringApplication对象

	// 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.namebootstrap,因此此时不会读取默认的名称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(注入ConfigurationPropertiesBindingPostProcessorConfigurationBeanFactoryMetadata)->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的流程
在这里插入图片描述

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值