Bootstrap 上下文启动流程源码分析
Spring Cloud 2020.0 之前,客户端通过 bootstrap 上下文在启动早期拉取远端配置。本文以源码视角拆解 PropertySourceBootstrapConfiguration 与 ContextIdApplicationContextInitializer 等关键组件,梳理 bootstrap 启动流程与配置优先级。
什么是 Bootstrap 上下文
Spring Boot 启动时通常只创建主上下文。开启 bootstrap 后,会先创建一个父上下文(bootstrap 上下文):
SpringApplication.run()
│
├─ 1. 创建 bootstrap 上下文(父)
│ ├─ 加载 bootstrap.yml
│ ├─ 从 Config Server 拉取配置
│ └─ 把远端配置注入父 Environment
├─ 2. 创建主上下文(子)
│ ├─ 以 bootstrap 上下文为 parent
│ └─ 继承已拉取的配置(占位符可解析)
└─ 3. 正常启动作用:在主上下文创建前就把远端配置拿到手,这样主上下文的 Bean 定义阶段就能解析 @Value 占位符、@ConfigurationProperties 绑定。
启动入口与初始化器
BootstrapApplicationListener
bootstrap 机制的启动由 BootstrapApplicationListener 触发,它通过 spring.factories 注册为 ApplicationListener<ApplicationEnvironmentPreparedEvent>:
// org.springframework.cloud.bootstrap.BootstrapApplicationListener
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
// 1. 检查是否启用 bootstrap(spring.cloud.bootstrap.enabled)
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
return;
}
// 2. 创建 bootstrap 属性源
// 从 bootstrap.yml 读取配置(name/profile/uri 等)
Map<String, Object> bootstrapProperties = getBootstrapProperties(environment);
// 3. 为 bootstrap 上下文构造独立 Environment
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles())
.environment(new StandardEnvironment());
builder.application().setEnvironment(...);
// 4. 设置 bootstrap 上下文(父上下文)
builder.parent(context); // 绑定为主上下文的父上下文
// 5. 注册 bootstrap 上下文使用的属性源
BootstrapImportSelectorConfiguration selector = new BootstrapImportSelectorConfiguration();
...
}
}关键点:
- bootstrap 上下文是嵌套上下文,通过
SpringApplicationBuilder.parent()绑定 - bootstrap 的属性源名固定为
bootstrap,位于 Environment 的最顶部(优先级最高) - 通过
spring.cloud.bootstrap.enabled=false可关闭
上下文创建
SpringApplication 启动(第一次)
│
├─ BootstrapApplicationListener 拦截 ApplicationEnvironmentPreparedEvent
├─ 创建 bootstrap 上下文(SpringApplicationBuilder 嵌套启动)
│ └─ 触发 PropertySourceBootstrapConfiguration
└─ bootstrap 完成后,回到主 SpringApplication 继续PropertySourceBootstrapConfiguration
bootstrap 上下文加载时,PropertySourceBootstrapConfiguration 负责从 Config Server 拉取配置:
// org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
@Configuration
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private List<PropertySourceLocator> propertySourceLocators; // 定位器列表
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 遍历所有 PropertySourceLocator,逐个拉取配置
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource<?> source = locator.locate(environment);
if (source == null) continue;
// 把拉到的 PropertySource 插入 Environment 顶部
environment.getPropertySources().addFirst(source);
}
}
}PropertySourceLocator:配置定位器
// org.springframework.cloud.bootstrap.config.PropertySourceLocator
public interface PropertySourceLocator {
PropertySource<?> locate(Environment environment);
}| 实现 | 作用 |
|---|---|
| ConfigServicePropertySourceLocator | 从 Config Server 拉取(核心) |
| VaultPropertySourceLocator | 从 Vault 拉取敏感配置 |
| DiscoveryClientConfigServiceBootstrapConfiguration 相关 | 先注册中心再找 Config Server |
ConfigServicePropertySourceLocator 核心流程
// org.springframework.cloud.config.client.ConfigServicePropertySourceLocator
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
private final ConfigClientProperties defaultProperties; // 从 bootstrap.yml 绑定
private final RestTemplate restTemplate;
@Override
@Retryable(interceptor = "configServerRetryInterceptor")
public PropertySource<?> locate(Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment);
// 1. 拼 URL:/order-service/dev/main
String url = getUrl(properties);
// 2. 调 Config Server
ResponseEntity<Environment> response = restTemplate.exchange(url, HttpMethod.GET, ...);
// 3. 把响应转成 PropertySource
return new EnvironmentPropertySource("configService", configServerEnvironment);
}
}@Retryable 保证 Config Server 暂时不可用时启动重试(默认 6 次,可配),避免启动失败。
ContextIdApplicationContextInitializer
bootstrap 上下文与主上下文区分靠 Context ID,由 ContextIdApplicationContextInitializer 设置:
// org.springframework.cloud.context.ContextIdApplicationContextInitializer
public class ContextIdApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ContextId contextId = getContextId(applicationContext);
// 把 contextId 作为属性写入 Environment
applicationContext.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("contextId", Map.of("spring.application.name", contextId.getContextId())));
}
}Context ID 生成规则
默认格式:{spring.application.name}:{port}:{random}
示例:order-service:8080:a1b2c3d4Context ID 的作用:
- 区分同一应用的不同实例(如 Bus 事件的目标匹配)
- 分布式缓存、分布式锁的 key 前缀
spring.application.name不存在时作为应用名兜底
配置优先级
bootstrap 机制下,Environment 的 PropertySource 顺序(高 → 低):
bootstrap(bootstrap.yml / bootstrap-{profile}.yml)
configService(Config Server 拉取的远端配置)
(主上下文加载时,这些作为父上下文属性被继承)
命令行参数 / 系统属性
application.yml / application-{profile}.yml
随机端口等默认值优先级高 优先级低
bootstrap > configService > 命令行 > application.yml理解要点:
- bootstrap 配置(如 Config Server 地址)本身不能在 Config Server 中,因为拉取它之前需要它
- 远端配置(configService)覆盖本地 application.yml 的同名配置
- 命令行参数优先级高于远端配置
新老机制对比(2020.0+)
Spring Cloud 2020.0 引入 config data import,bootstrap 机制默认禁用:
| 对比 | Bootstrap 机制(旧) | Config Data Import(新) |
|---|---|---|
| 触发 | BootstrapApplicationListener | ConfigDataEnvironmentPostProcessor |
| 配置位置 | bootstrap.yml | application.yml 中 spring.config.import |
| 上下文 | 额外 bootstrap 父上下文 | 无额外上下文 |
| 占位符 | 主上下文启动前已注入 | import 的配置先于 Bean 创建注入 |
| 启用方式 | 默认(可关闭) | 默认禁用,需显式 import |
# 新版:无需 bootstrap.yml
spring:
config:
import: configserver:http://config-server:8888常见问题
- 新版为什么去掉 bootstrap? 双上下文复杂度高、与 Spring Boot 新配置机制重叠;Config Data Import 更简单直接。
- bootstrap 配置没加载? 检查
spring.cloud.bootstrap.enabled是否被关闭、bootstrap.yml 位置是否正确、是否有 config server 地址。 - Config Server 启动时不可用?
@Retryable重试默认 6 次,可通过spring.cloud.config.fail-fast=true快速失败或调整重试次数。 - contextId 有什么用? 区分实例、Bus 定向刷新、缓存 key 隔离都依赖它。