提前说明:关于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 contextif (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 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);}

创建名称为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 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.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 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(注入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 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文件的?相关推荐

  1. spring cloud连载第一篇之bootstrap context

    1. Spring Cloud Context: Application Context Services(应用上下文服务) 1.1 The Bootstrap Application Context ...

  2. 告诉老默我想学Spring Cloud了(新手篇):从0到1搭建Spring Cloud项目(实际项目开发的浓缩精华版)

    告诉老默我想学Spring Cloud了(新手篇):从0到1搭建Spring Cloud项目 一.前言 二.如何选择版本 2.1 SpringCloud 和 Spring Boot 版本选型 2.1. ...

  3. springboot 读取bootStrap.properties流程

    一.bootstrap.properties其实是属于spring-cloud的一个环境配置,示例如下 需要添加MAVEN包,否则不会加载bootStrap.properties. <depen ...

  4. 自己动手,使用Spring Initializr从零开始搭建Spring Cloud项目

    新建Project 这里使用的开发工具是IDEA,JDK版本1.8. 打开IDEA开发工具,File -> New -> Project 然后一步步往下设置,然后到这一步,选择Spring ...

  5. Spring Cloud——Spring Cloud Alibaba 2021 Nacos Config bootstrap 配置文件失效解决方案

    基本概念 微服务是基于Spring Cloud框架搭建的,Spring Cloud Config作为服务配置中心. 业务服务只配置服务名称.启用环境和config的URL地址,其他都配置在配置中心,例 ...

  6. Linux中部署Spring Cloud项目

    Linux中部署Spring Cloud项目 文章为本人在学习的过程中,记录部署过程,仅供参考学习.因本人经验不足,教程或有不妥之处,还望指正. 保姆级教程,敬请食用!!! 简介 在学习过程中,部署时 ...

  7. 用idea搭建一个Spring Cloud项目(含代码)

    目录 目标 相关术语介绍 实战 搭建父工程(聚合工程) 搭建注册中心微服务 搭建生产者和消费者微服务 新增Eureka用户认证 新增健康检测 手动维护注册列表 剔除微服务 up微服务 down微服务 ...

  8. IntelliJ 启动不同端口的两个spring cloud项目

    IntelliJ 启动不同端口的两个spring cloud项目 1,使用maven进行clean package 2,在Terminal界面,输入java -jar xxx.jar --server ...

  9. Spring Boot项目实战之aliyunOss对象储存#文件上传接口实现(代码齐全)

    项目环境:   开发工具:IDEA(jdk1.8)   模块类型:Mavan项目   OSS版本:2.8.3   开发者文档链接:https://help.aliyun.com/document_de ...

最新文章

  1. 无法解析的外部符号 “public: __cdecl nvinfer1::YoloPluginCreator::YoloPluginCreator
  2. python 下划线转驼峰_json字符串中key值下划线命名转换为驼峰命名
  3. MATLAB学习笔记(二)
  4. flume高可用-failover-模型分析
  5. c语言配电自动化,我是电气工程及其自动化专业的要学C语言吗?
  6. 为什么Java中类方法不能访问实例方法
  7. [置顶] J2EE (八) 策略模式+装饰模式+反射(java)
  8. 帮助文档的制作(控制台、eclipse两种方式)
  9. python解题_python实现用户答题功能
  10. read properties
  11. c 语言 封装dll_C#封装YOLOv4算法进行目标检测
  12. Windows Phone开发(33):路径之其它Geometry 转:http://blog.csdn.net/tcjiaan/article/details/7483835...
  13. clion使用之如何在编译运行多个程序(以cpp为例)
  14. unity开宝箱动画_Unity动画库插件iTween介绍
  15. uniapp下微信小程序超过2MB大小限制的解决方法
  16. Unity开发教程 打造战棋手游《天地劫》
  17. 计算机中的原码、反码和补码计算
  18. UltraEdit32常用快捷键
  19. IIS WebDAV安全配置
  20. 程序员的五一是怎么过的?除了狗粮还是狗粮?

热门文章

  1. 华氏度与摄氏度的转化(C语言)
  2. CSS3笔记(菜鸟教程)
  3. 【​观察】中国云计算产业的下半场 京东云正在下一盘怎样的大棋?
  4. 插入排序一块说说-很合适~~~二分查找和折半
  5. Excel 文件怎么批量插入首页、扉页、尾页?怎么将某个 Excel 文件批量插入到其它 Excel 文件的指定位置?
  6. IDEA 查看项目编码
  7. 数据中台,什么是数据中台?
  8. 单反相机的传奇—佳能单反50年辉煌之路(连载十七)
  9. 苹果手机怎么查看足迹_苹果手机打电话怎么录音
  10. 初识QT之QTWidget窗口