找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2029

积分

0

好友

267

主题
发表于 3 天前 | 查看: 16| 回复: 0

在使用SpringCloud配置中心时,你是否也有过这些困惑?

  • SpringCloud是在什么时候、以何种方式去拉取配置中心的?
  • 客户端的配置信息为什么一定要写在 bootstrap.ymlbootstrap.properties 文件中?
  • 加了 @RefreshScope 注解的Bean,其属性是如何实现动态刷新的?
  • 像Nacos、Consul这些开源的配置中心,是如何与SpringCloud无缝整合的?

本文将围绕这些核心问题,深入SpringCloud与Spring Boot的源码,为你揭开配置中心背后的运作机制。理解了这些原理,无论是使用还是排查相关问题,你都会更加得心应手。

从SpringBoot的启动过程说起

一切的起点都源于SpringBoot的启动。SpringApplication.run() 方法是整个启动流程的核心入口。

SpringApplication.run() 方法核心代码截图

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

ApplicationContext刷新过程三阶段:刷新前、刷新中、刷新后

  1. ApplicationContext刷新前阶段:此阶段主要完成三件事:

    • 准备Environment(重点,后面会详解),即准备SpringBoot外部化配置的核心对象。
    • 创建 ApplicationContext(应用上下文)。
    • ApplicationContext 做一些初始化准备工作。

    刷新前阶段核心代码:创建Environment、ApplicationContext及prepareContext

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

    刷新阶段核心代码:refreshContext(context)方法调用

  3. ApplicationContext刷新后阶段:主要是启动后的收尾工作,例如执行 CommandLineRunner 等。

那么,你认为在上述三个阶段中,哪个阶段最有可能从配置中心拉取配置呢?

答案显然是 刷新前阶段。原因很直接:这个阶段负责准备 Environment,而 Environment 正是所有外部化配置(包括本地配置文件和远程配置中心)的承载者。只有在这个阶段将配置中心的配置加载到 Environment 中,后续在容器刷新阶段创建Bean时,才能成功注入这些配置值。

同理,本地配置文件(如 application.yml)的加载也是在这个阶段完成的。

接下来,我们就聚焦于 准备Environment 这一核心操作。

准备Environment:配置拉取的舞台

结论先行:prepareEnvironment 方法是加载配置(无论是本地还是远程)的关键。

prepareEnvironment方法实现代码

在深入这个方法前,需要了解一些关键的前置操作

关键的前置操作:监听器的加载与装配

SpringApplication 实例化时,它会通过 SpringFactoriesLoaderspring.factories 配置文件中加载两类重要的组件:

  1. ApplicationListener (应用监听器):例如 ConfigFileApplicationListener(处理本地配置文件)和 BootstrapApplicationListener(处理配置中心)。

    SpringApplication构造函数中加载ApplicationListener

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

    getRunListeners方法加载SpringApplicationRunListener
    EventPublishingRunListener构造函数

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

SpringApplicationRunListener与ApplicationListener加载关系示意图

prepareEnvironment的核心:事件驱动加载配置

现在回到 prepareEnvironment 方法。该方法在创建 Environment 对象后,会执行一行关键代码:

listeners.environmentPrepared(environment);

这行代码最终会触发 EventPublishingRunListener#environmentPrepared 方法。

EventPublishingRunListener.environmentPrepared方法发布事件

该方法会发布一个 ApplicationEnvironmentPreparedEvent 事件。对此事件,有两个至关重要的监听器会做出响应:

  • ConfigFileApplicationListener:负责加载本地配置文件(如 application.yml)的配置到 Environment
  • BootstrapApplicationListener这就是与配置中心交互的入口,也是本文的重点。

Environment准备阶段事件监听与处理流程图

至此,我们找到了SpringCloud配置中心在项目启动时拉取配置的逻辑入口——BootstrapApplicationListener

SpringCloud拉取配置的“妙计”

BootstrapApplicationListener 被触发后,它的核心操作是:创建一个额外的、独立的Spring容器

BootstrapApplicationListener创建SpringApplicationBuilder并运行

这个容器是专门用来与配置中心“对话”的父容器。在创建时,有两个关键设置:

  1. 设置专属配置文件名:默认设置为 bootstrap。这也就完美解释了为什么配置中心的连接信息(如服务器地址、命名空间等)必须定义在 bootstrap.yml 中,因为只有这个文件会被这个特殊的父容器加载。

    设置spring.config.name为bootstrap

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

    builder.sources方法设置BootstrapImportSelectorConfiguration类

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

BootstrapImportSelector.selectImports方法加载配置类

该方法会加载所有 spring.factories 文件中,键为 org.springframework.cloud.bootstrap.BootstrapConfiguration 所对应的配置类。

这里的 @BootstrapConfiguration 机制,与Spring Boot的 @EnableAutoConfiguration 自动装配思想类似,都是用于集中式地导入一批配置类。

因此,这个“配置中心父容器”的核心任务就是两件事:

  1. 加载 bootstrap 配置文件。
  2. 加载所有通过 @BootstrapConfiguration 声明的配置类。

Bootstrap容器加载配置文件及BootstrapConfiguration配置类示意图

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

spring.factories文件中BootstrapConfiguration配置项
PropertySourceBootstrapConfiguration配置类定义

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

PropertySourceLocator接口定义

PropertySourceLocator 接口是理解整合原理的关键。其Javadoc写道:

Strategy for locating (possibly remote) property sources for the Environment.
(一种为Environment定位(可能是远程的)属性源的策略。)

“possibly remote(可能是远程的)”这个描述,几乎明示了它的用途——从远程配置中心获取配置。不同的配置中心(如Nacos、Consul)要接入SpringCloud,就必须实现这个接口。

在父容器初始化的某个阶段,会遍历调用容器中所有的 PropertySourceLocator 实现类的 locate 方法。

PropertySourceBootstrapConfiguration.initialize方法遍历调用PropertySourceLocator

每个实现类负责从自己的配置中心拉取配置,并将其封装成 PropertySource 返回。最后,这些远程配置会被加入到主应用的 Environment 中。这样,在主容器的刷新阶段,所有Bean就能正常注入来自配置中心的属性值了。

本节小结

总结一下项目启动时加载配置中心配置的完整流程:

  1. 主应用启动,在准备 Environment 阶段发布 ApplicationEnvironmentPreparedEvent 事件。
  2. BootstrapApplicationListener 监听该事件,创建一个专用的“父”Spring容器
  3. 该父容器只加载 bootstrap 配置文件和所有 @BootstrapConfiguration 标注的配置类
  4. 在父容器中,PropertySourceBootstrapConfiguration 配置类生效,它汇集了所有 PropertySourceLocator 实现(即各个配置中心的客户端)。
  5. 调用这些 PropertySourceLocator,从各自的配置中心拉取配置,并注入到主应用的 Environment 中。
  6. 主容器继续启动,Bean使用包含远程配置的 Environment 进行初始化。

SpringCloud配置中心启动加载全链路示意图

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)并调用其方法。

@RefreshScope Bean代理调用关系示意图

那么,为何要设计如此复杂的结构?这就要说到刷新流程。

刷新事件的触发与处理

SpringCloud定了一套规范:当配置中心客户端感知到服务端配置变化时,必须发布一个 RefreshEvent 事件。

配置变化触发RefreshEvent事件流程

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

RefreshEventListener监听RefreshEvent事件

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

ContextRefresher中重新拉取配置的代码

“移花接木”实现属性刷新

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

刷新时销毁真实对象,保留代理对象

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

代理对象调用时,重新创建拥有新配置的真实对象

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

动态刷新完整流程示意图

深入@RefreshScope的实现机制

@RefreshScope 本质上是一个衍生注解,其核心是 @Scope("refresh")

@RefreshScope注解定义,核心是@Scope(“refresh”)

在Spring中,@Scope 用于定义Bean的作用域。除了常见的 singletonprototype,还可以自定义作用域,如 sessionrequest,以及这里的 refresh。这些自定义作用域的实现原理相似:

  • 生成代理与目标对象:如前所述,会注册两个Bean。
  • 存储位置不同:单例Bean保存在一级缓存中;而自定义作用域(如 refresh)的Bean,由其对应的 Scope 实现类管理。

Spring 定义了 Scope 接口来管理不同作用域内Bean的生命周期。

Scope接口定义

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

IDE中显示的Scope实现类列表,包含RefreshScope

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

ContextRefresher调用RefreshScope.refreshAll方法

开源配置中心如何整合SpringCloud?

基于前面的原理分析,一个开源配置中心要整合SpringCloud,其实只需要完成两件事:

第一,实现 PropertySourceLocator 接口,并确保相关的配置类通过 @BootstrapConfiguration 机制,被加载到前面提到的“父容器”中。这是为了启动拉取配置

第二,实现配置变更的监听,在监听到配置变化时,发布 RefreshEvent 事件。这部分的Bean通常通过标准的 @EnableAutoConfiguration 自动装配到主容器中。这是为了动态刷新配置

我们以 Spring Cloud Alibaba Nacos Config 为例,看看它是如何做的。

查看Nacos的 spring.factories 文件:

Nacos的spring.factories文件内容,包含BootstrapConfiguration和AutoConfiguration

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

    NacosConfigBootstrapConfiguration配置类,定义NacosPropertySourceLocator

    NacosPropertySourceLocator 正是 PropertySourceLocator 接口的实现。

    NacosPropertySourceLocator类定义

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

    NacosConfigAutoConfiguration中定义NacosContextRefresher

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

    NacosContextRefresher发布RefreshEvent事件代码

通过以上两步,Nacos就完美地融入了SpringCloud配置中心的生态。Consul、ZooKeeper等配置中心的整合方式也大同小异。

值得注意的是,并非所有配置中心都遵循这套规范,例如Apollo早期版本就没有直接实现 PropertySourceLocator,需要额外的适配层。但理解这套标准机制后,你甚至可以自己为任何配置中心编写适配器。

总结

SpringCloud配置中心的核心原理,巧妙地将Spring Boot的启动扩展机制和Spring框架的作用域(Scope)机制结合起来,通过事件驱动和分层容器的设计,实现了配置的集中管理、动态刷新和多源适配。通过剖析 PropertySourceLocator@RefreshScope 这两个核心扩展点,我们不仅掌握了使用技巧,更能洞察其设计精髓。无论你是使用主流的Nacos,还是其他配置中间件,或是需要在复杂的分布式系统中排查配置问题,这些底层知识都将为你提供坚实的支撑。希望这篇深度解析能帮助你更好地驾驭SpringCloud配置中心。

欢迎在云栈社区交流讨论更多关于Java和微服务架构的技术问题。




上一篇:Nacos整合Spring Cloud注册中心原理:ServiceRegistry与Ribbon适配详解
下一篇:Spring Cloud 配置中心实践:手写简化版并整合动态刷新机制
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-10 12:45 , Processed in 0.587724 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表