在使用SpringCloud配置中心时,你是否也有过这些困惑?
- SpringCloud是在什么时候、以何种方式去拉取配置中心的?
- 客户端的配置信息为什么一定要写在
bootstrap.yml 或 bootstrap.properties 文件中?
- 加了
@RefreshScope 注解的Bean,其属性是如何实现动态刷新的?
- 像Nacos、Consul这些开源的配置中心,是如何与SpringCloud无缝整合的?
本文将围绕这些核心问题,深入SpringCloud与Spring Boot的源码,为你揭开配置中心背后的运作机制。理解了这些原理,无论是使用还是排查相关问题,你都会更加得心应手。
从SpringBoot的启动过程说起
一切的起点都源于SpringBoot的启动。SpringApplication.run() 方法是整个启动流程的核心入口。

这个过程大致可以划分为三个阶段:

-
ApplicationContext刷新前阶段:此阶段主要完成三件事:
- 准备Environment(重点,后面会详解),即准备SpringBoot外部化配置的核心对象。
- 创建
ApplicationContext(应用上下文)。
- 为
ApplicationContext 做一些初始化准备工作。

-
ApplicationContext刷新阶段:核心是调用 ApplicationContext#refresh() 方法来刷新容器、初始化所有单例Bean。

-
ApplicationContext刷新后阶段:主要是启动后的收尾工作,例如执行 CommandLineRunner 等。
那么,你认为在上述三个阶段中,哪个阶段最有可能从配置中心拉取配置呢?
答案显然是 刷新前阶段。原因很直接:这个阶段负责准备 Environment,而 Environment 正是所有外部化配置(包括本地配置文件和远程配置中心)的承载者。只有在这个阶段将配置中心的配置加载到 Environment 中,后续在容器刷新阶段创建Bean时,才能成功注入这些配置值。
同理,本地配置文件(如 application.yml)的加载也是在这个阶段完成的。
接下来,我们就聚焦于 准备Environment 这一核心操作。
准备Environment:配置拉取的舞台
结论先行:prepareEnvironment 方法是加载配置(无论是本地还是远程)的关键。

在深入这个方法前,需要了解一些关键的前置操作。
关键的前置操作:监听器的加载与装配
在 SpringApplication 实例化时,它会通过 SpringFactoriesLoader 从 spring.factories 配置文件中加载两类重要的组件:
-
ApplicationListener (应用监听器):例如 ConfigFileApplicationListener(处理本地配置文件)和 BootstrapApplicationListener(处理配置中心)。

-
SpringApplicationRunListener:此类只有一个默认实现——EventPublishingRunListener。它在构造时,会创建一个事件广播器(SimpleApplicationEventMulticaster),并将上一步加载的所有 ApplicationListener 添加进去。


简单来说,EventPublishingRunListener 充当了事件发布的中介,它将Spring Boot启动过程中的关键节点事件,广播给所有注册的 ApplicationListener。

prepareEnvironment的核心:事件驱动加载配置
现在回到 prepareEnvironment 方法。该方法在创建 Environment 对象后,会执行一行关键代码:
listeners.environmentPrepared(environment);
这行代码最终会触发 EventPublishingRunListener#environmentPrepared 方法。

该方法会发布一个 ApplicationEnvironmentPreparedEvent 事件。对此事件,有两个至关重要的监听器会做出响应:
ConfigFileApplicationListener:负责加载本地配置文件(如 application.yml)的配置到 Environment。
BootstrapApplicationListener:这就是与配置中心交互的入口,也是本文的重点。

至此,我们找到了SpringCloud配置中心在项目启动时拉取配置的逻辑入口——BootstrapApplicationListener。
SpringCloud拉取配置的“妙计”
BootstrapApplicationListener 被触发后,它的核心操作是:创建一个额外的、独立的Spring容器。

这个容器是专门用来与配置中心“对话”的父容器。在创建时,有两个关键设置:
-
设置专属配置文件名:默认设置为 bootstrap。这也就完美解释了为什么配置中心的连接信息(如服务器地址、命名空间等)必须定义在 bootstrap.yml 中,因为只有这个文件会被这个特殊的父容器加载。

-
导入关键配置类:通过 BootstrapImportSelectorConfiguration 类,间接导入了 BootstrapImportSelector。

BootstrapImportSelector 实现了 ImportSelector 接口。当父容器启动时,会调用其 selectImports 方法。

该方法会加载所有 spring.factories 文件中,键为 org.springframework.cloud.bootstrap.BootstrapConfiguration 所对应的配置类。
这里的 @BootstrapConfiguration 机制,与Spring Boot的 @EnableAutoConfiguration 自动装配思想类似,都是用于集中式地导入一批配置类。
因此,这个“配置中心父容器”的核心任务就是两件事:
- 加载
bootstrap 配置文件。
- 加载所有通过
@BootstrapConfiguration 声明的配置类。

在 spring-cloud-context 包中,@BootstrapConfiguration 导入了一个至关重要的配置类:PropertySourceBootstrapConfiguration。


这个配置类中,通过 @Autowired 注入了一个 List<PropertySourceLocator> 集合。

PropertySourceLocator 接口是理解整合原理的关键。其Javadoc写道:
Strategy for locating (possibly remote) property sources for the Environment.
(一种为Environment定位(可能是远程的)属性源的策略。)
“possibly remote(可能是远程的)”这个描述,几乎明示了它的用途——从远程配置中心获取配置。不同的配置中心(如Nacos、Consul)要接入SpringCloud,就必须实现这个接口。
在父容器初始化的某个阶段,会遍历调用容器中所有的 PropertySourceLocator 实现类的 locate 方法。

每个实现类负责从自己的配置中心拉取配置,并将其封装成 PropertySource 返回。最后,这些远程配置会被加入到主应用的 Environment 中。这样,在主容器的刷新阶段,所有Bean就能正常注入来自配置中心的属性值了。
本节小结
总结一下项目启动时加载配置中心配置的完整流程:
- 主应用启动,在准备
Environment 阶段发布 ApplicationEnvironmentPreparedEvent 事件。
BootstrapApplicationListener 监听该事件,创建一个专用的“父”Spring容器。
- 该父容器只加载
bootstrap 配置文件和所有 @BootstrapConfiguration 标注的配置类。
- 在父容器中,
PropertySourceBootstrapConfiguration 配置类生效,它汇集了所有 PropertySourceLocator 实现(即各个配置中心的客户端)。
- 调用这些
PropertySourceLocator,从各自的配置中心拉取配置,并注入到主应用的 Environment 中。
- 主容器继续启动,Bean使用包含远程配置的
Environment 进行初始化。

Bean属性动态刷新的“移花接木”术
配置拉取只是第一步,动态刷新才是配置中心的魅力所在。要实现属性的动态刷新,需要在Bean上添加 @RefreshScope 注解。
@RefreshScope
@Service
public class UserService {
@Value("${sanyou.username}")
private String username;
public String getUsername() {
return username;
}
}
核心秘密在于:一个加了 @RefreshScope 的Bean(如 UserService),在Spring容器中实际会存在两个Bean实例:
- Bean 1 (代理对象):一个动态生成的代理对象,Bean名称是
userService,作用域为默认的单例(singleton)。我们代码中注入和直接使用的,正是这个代理对象。
- Bean 2 (目标对象):真实的
UserService 实例,Bean名称是 scopedTarget.userService,作用域为 refresh。
当我们调用 userService.getUsername() 时,实际上是代理对象(Bean 1)在工作,它会去找到真实的目标对象(Bean 2)并调用其方法。

那么,为何要设计如此复杂的结构?这就要说到刷新流程。
刷新事件的触发与处理
SpringCloud定了一套规范:当配置中心客户端感知到服务端配置变化时,必须发布一个 RefreshEvent 事件。

SpringCloud内置的 RefreshEventListener 专门监听此事件。

一旦收到事件,SpringCloud便会重新拉取配置。这个拉取过程与启动时类似,同样会通过 BootstrapApplicationListener 等机制,重建那个“父容器”,并获取最新的配置。

“移花接木”实现属性刷新
获取到最新配置后,真正的“魔术”开始了:SpringCloud会销毁所有作用域为 refresh 的Bean(即上述的 Bean 2,目标对象),而代理对象(Bean 1)则被保留。

当程序再次通过代理对象调用方法时,代理对象发现目标对象(Bean 2)不存在了,就会触发重建。在重建过程中,由于 Environment 中已经是最新的配置,因此新创建出来的目标对象(Bean 2)自然就被注入了最新的属性值。

此后,所有的调用都经由代理对象转发给这个新创建的目标对象,从而实现了属性的“动态刷新”。感知上像是对象的属性值变了,实际上是旧对象被销毁,新对象被创建,这是一招漂亮的“移花接木”。

深入@RefreshScope的实现机制
@RefreshScope 本质上是一个衍生注解,其核心是 @Scope("refresh")。

在Spring中,@Scope 用于定义Bean的作用域。除了常见的 singleton 和 prototype,还可以自定义作用域,如 session、request,以及这里的 refresh。这些自定义作用域的实现原理相似:
- 生成代理与目标对象:如前所述,会注册两个Bean。
- 存储位置不同:单例Bean保存在一级缓存中;而自定义作用域(如
refresh)的Bean,由其对应的 Scope 实现类管理。
Spring 定义了 Scope 接口来管理不同作用域内Bean的生命周期。

对于 refresh 作用域,其具体的实现类是 RefreshScope。所有标注了 @RefreshScope 的Bean,其目标对象(Bean 2)都存储在这个 RefreshScope 实例中。

因此,所谓的“销毁”操作,其实就是调用 RefreshScope 的 destroy() 或 refreshAll() 方法,将其内部管理的目标对象缓存清空。后续的 ContextRefresher 正是通过调用 this.scope.refreshAll() 来完成这一步的。

开源配置中心如何整合SpringCloud?
基于前面的原理分析,一个开源配置中心要整合SpringCloud,其实只需要完成两件事:
第一,实现 PropertySourceLocator 接口,并确保相关的配置类通过 @BootstrapConfiguration 机制,被加载到前面提到的“父容器”中。这是为了启动拉取配置。
第二,实现配置变更的监听,在监听到配置变化时,发布 RefreshEvent 事件。这部分的Bean通常通过标准的 @EnableAutoConfiguration 自动装配到主容器中。这是为了动态刷新配置。
我们以 Spring Cloud Alibaba Nacos Config 为例,看看它是如何做的。
查看Nacos的 spring.factories 文件:

-
实现启动拉取(第一件事):
NacosConfigBootstrapConfiguration 类通过 @BootstrapConfiguration 被导入父容器。它声明了 NacosPropertySourceLocator 这个Bean。

NacosPropertySourceLocator 正是 PropertySourceLocator 接口的实现。

-
实现动态刷新(第二件事):
NacosConfigAutoConfiguration 类通过 @EnableAutoConfiguration 被导入主容器。它声明了 NacosContextRefresher 这个Bean。

NacosContextRefresher 在内部监听Nacos服务器的配置变化,一旦变化,就发布 RefreshEvent 事件。

通过以上两步,Nacos就完美地融入了SpringCloud配置中心的生态。Consul、ZooKeeper等配置中心的整合方式也大同小异。
值得注意的是,并非所有配置中心都遵循这套规范,例如Apollo早期版本就没有直接实现 PropertySourceLocator,需要额外的适配层。但理解这套标准机制后,你甚至可以自己为任何配置中心编写适配器。
总结
SpringCloud配置中心的核心原理,巧妙地将Spring Boot的启动扩展机制和Spring框架的作用域(Scope)机制结合起来,通过事件驱动和分层容器的设计,实现了配置的集中管理、动态刷新和多源适配。通过剖析 PropertySourceLocator 和 @RefreshScope 这两个核心扩展点,我们不仅掌握了使用技巧,更能洞察其设计精髓。无论你是使用主流的Nacos,还是其他配置中间件,或是需要在复杂的分布式系统中排查配置问题,这些底层知识都将为你提供坚实的支撑。希望这篇深度解析能帮助你更好地驾驭SpringCloud配置中心。
欢迎在云栈社区交流讨论更多关于Java和微服务架构的技术问题。