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 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 contextif (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 itbootstrapMap.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 refreshif (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 importsClass<?> 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 duplicatesList<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 implementsApplicationContextInitializer<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() };@Overridepublic 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 contextaddAncestorInitializer(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容器中获取ApplicationContextInitializerList<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的流程
Spring Cloud项目是如何读取bootstrap.properties文件的?相关推荐
- spring cloud连载第一篇之bootstrap context
1. Spring Cloud Context: Application Context Services(应用上下文服务) 1.1 The Bootstrap Application Context ...
- 告诉老默我想学Spring Cloud了(新手篇):从0到1搭建Spring Cloud项目(实际项目开发的浓缩精华版)
告诉老默我想学Spring Cloud了(新手篇):从0到1搭建Spring Cloud项目 一.前言 二.如何选择版本 2.1 SpringCloud 和 Spring Boot 版本选型 2.1. ...
- springboot 读取bootStrap.properties流程
一.bootstrap.properties其实是属于spring-cloud的一个环境配置,示例如下 需要添加MAVEN包,否则不会加载bootStrap.properties. <depen ...
- 自己动手,使用Spring Initializr从零开始搭建Spring Cloud项目
新建Project 这里使用的开发工具是IDEA,JDK版本1.8. 打开IDEA开发工具,File -> New -> Project 然后一步步往下设置,然后到这一步,选择Spring ...
- Spring Cloud——Spring Cloud Alibaba 2021 Nacos Config bootstrap 配置文件失效解决方案
基本概念 微服务是基于Spring Cloud框架搭建的,Spring Cloud Config作为服务配置中心. 业务服务只配置服务名称.启用环境和config的URL地址,其他都配置在配置中心,例 ...
- Linux中部署Spring Cloud项目
Linux中部署Spring Cloud项目 文章为本人在学习的过程中,记录部署过程,仅供参考学习.因本人经验不足,教程或有不妥之处,还望指正. 保姆级教程,敬请食用!!! 简介 在学习过程中,部署时 ...
- 用idea搭建一个Spring Cloud项目(含代码)
目录 目标 相关术语介绍 实战 搭建父工程(聚合工程) 搭建注册中心微服务 搭建生产者和消费者微服务 新增Eureka用户认证 新增健康检测 手动维护注册列表 剔除微服务 up微服务 down微服务 ...
- IntelliJ 启动不同端口的两个spring cloud项目
IntelliJ 启动不同端口的两个spring cloud项目 1,使用maven进行clean package 2,在Terminal界面,输入java -jar xxx.jar --server ...
- Spring Boot项目实战之aliyunOss对象储存#文件上传接口实现(代码齐全)
项目环境: 开发工具:IDEA(jdk1.8) 模块类型:Mavan项目 OSS版本:2.8.3 开发者文档链接:https://help.aliyun.com/document_de ...
最新文章
- 无法解析的外部符号 “public: __cdecl nvinfer1::YoloPluginCreator::YoloPluginCreator
- python 下划线转驼峰_json字符串中key值下划线命名转换为驼峰命名
- MATLAB学习笔记(二)
- flume高可用-failover-模型分析
- c语言配电自动化,我是电气工程及其自动化专业的要学C语言吗?
- 为什么Java中类方法不能访问实例方法
- [置顶] J2EE (八) 策略模式+装饰模式+反射(java)
- 帮助文档的制作(控制台、eclipse两种方式)
- python解题_python实现用户答题功能
- read properties
- c 语言 封装dll_C#封装YOLOv4算法进行目标检测
- Windows Phone开发(33):路径之其它Geometry 转:http://blog.csdn.net/tcjiaan/article/details/7483835...
- clion使用之如何在编译运行多个程序(以cpp为例)
- unity开宝箱动画_Unity动画库插件iTween介绍
- uniapp下微信小程序超过2MB大小限制的解决方法
- Unity开发教程 打造战棋手游《天地劫》
- 计算机中的原码、反码和补码计算
- UltraEdit32常用快捷键
- IIS WebDAV安全配置
- 程序员的五一是怎么过的?除了狗粮还是狗粮?