用法
- 使用@RefreshScope+@Value,实现动态刷新
@RestController
@RefreshScope
public class TestController {
@Value("${cls.name}")
private String clsName;
}
- 使用ConfigurationProperties,在service通过@Autowired注入使用
@Data
@ConfigurationProperties(prefix = "redis")
@Component
public class RedisProperties {
private String userName;
private String password;
private String url;
热更新流程
CacheData#checkListenerMd5
-
cacheData监听配置的Md5变化、使用通知监听器告知nacos内容刷新器NacosContextRefresher(spring依赖)执行刷新。
首先,Nacos客户端通过长轮询机制监听配置变化。当配置更新时,checkListenerMd5 方法会遍历所有监听器,检查配置内容的MD5是否发生变化。
若MD5发生变化,safeNotifyListener 被调用来通知变化。其次,这是观察者模式的实现
NacosContextRefresher
nacos内容刷新器会健壮地为可刷新源注册新的Nacos监听器。内部重写innerReceive,会增加刷新计数,记录刷新历史,并发布一个RefreshEvent刷新事件。
NacosContextRefresher在应用启动时注册Nacos监听器,内部重写了innerReceive方法以监听配置变化事件。当配置变化,它触发应用上下文中相关Bean的刷新。后续更新也是对innerReceive的改进和丰富。比如RefreshEventListener.refreshEnvironment.addConfigFilesToEnvironment方法在新版叫updateEnvironment()
-
innerReceive
- 增加刷新计数器 refreshCountIncrement。
- 将新的配置信息添加到 nacosRefreshHistory 来记录刷新历史。
- 在 NacosSnapshotConfigManager 中保存配置的快照。
- 发布一个 RefreshEvent,触发Spring上下文中相关的事件。
- 如果日志级别为debug,输出刷新配置的详细信息。
观察者模式
-
使用onApplicationEvent.ready.compareAndSet(false, true)健壮注册监听器监听配置变化事件
-
然后健壮遍历所有可刷新的Nacos属性源(NacosPropertySource),并注册为每个刷新源注册新的Nacos监听器
-
根据group和dataId注册一个新的Nacos监听器,当NacosContextRefresher发布刷新事件后会将新创建的监听器添加到Nacos配置服务。
-
是的,这段代码体现了观察者模式的特征。
在观察者模式中,有两个主要角色:主题(Subject)和观察者(Observer)。主题维护一系列依赖于它的观察者,当主题的状态发生变化时,会自动通知所有观察者。
在这个场景中:
- Nacos配置中心可以被看作是主题(Subject)。它维护着一系列配置信息,当这些配置信息发生变化时,需要通知所有相关的观察者。
- 应用程序中的各种监听器(实现了Listener接口的类,如AbstractSharedListener)可以被看作是观察者(Observer)。这些监听器依赖于Nacos配置中心的状态(即配置信息)。当配置信息发生变化时,Nacos配置中心会通知这些监听器,它们随后可以根据新的配置信息进行相应的操作,比如刷新应用中的配置。
通过registerNacosListener方法,应用程序为每一个关心配置变更的部分(比如特定的dataId和group对应的配置)注册一个监听器。当Nacos配置中心的相关配置发生变化时,这些注册的监听器会被通知,并执行相应的更新操作,如重新加载配置。
因此,这段代码通过实现和应用观察者模式,使得应用程序能够动态地响应外部配置的变更,从而实现了配置的动态管理和更新。
RefreshEventListener
RefreshEventListener监听到RefreshEvent,会执行this.refresh.refresh();
ContextRefresher#refresh
add方法版
-
refresh方法:
这是一个同步方法,用于刷新整个Spring应用环境。
首先调用refreshEnvironment来刷新环境变量,返回值是所有发生变化的配置键(keys)的集合。
接着调用this.scope.refreshAll()刷新所有的scoped beans,确保它们能够在新环境下重新初始化。
-
refreshEnvironment方法:
同样是一个同步方法,专门用于刷新环境变量。
首先记录当前环境属性源中的所有属性值。
调用addConfigFilesToEnvironment方法将新的配置文件添加到环境中。
计算环境变化之前和之后的差异,并生成一个包含所有变化键的集合。
发布一个EnvironmentChangeEvent事件,以通知应用中其他部分环境已经发生化。
返回所有变化的配置键集合。
-
addConfigFilesToEnvironment方法(新版叫updateEnvironment()):
- 此方法用于将新的配置文件添加到Spring环境中。
- 首先创建一个新的StandardEnvironment实例,复制当前环境的配置。
- 通过构建一个新的Spring应用实例并只添加对环境有影响的监听器,如BootstrapApplicationListener和ConfigFileApplicationListener,以加载和应用新的配置文件。
- 一旦新的应用实例运行完毕,将从中提取更新后的属性源,并将它们合并回原始应用上下文的环境中,确保新的配置能够被正确应用。
- 如果过程中创建了新的应用上下文(ConfigurableApplicationContext),这个上下文会在结束时被关闭,以释放资源。
-
-
设计模式
这段代码主要体现了几种设计模式:
- 模板方法模式(Template Method): 在refresh和refreshEnvironment方法中可以看到这种模式的影子。这些方法定义了操作的框架或流程(即先刷新环境,然后刷新作用域,最后返回变化的键)。具体步骤(如刷新环境的具体实现)在其他方法(如addConfigFilesToEnvironment)中定义。这允许在不改变算法结构的情况下重定义算法的某些步骤。
- 命令模式(Command): 在这段代码中,发布事件(如EnvironmentChangeEvent)可以被看作是命令模式的一种体现。在这里,一个事件(命令)被创建并通过Spring事件发布机制发送,相当于“命令”的执行。事件监听器则相当于命令的接收者,根据事件(命令)执行相应的操作。
- 观察者模式(Observer): 这在Spring的事件监听机制中得到体现。当环境发生变化时(如配置更新),会发布一个EnvironmentChangeEvent。这个事件的发布类似于主题(Subject)通知观察者(Observer)状态改变。应用中的其他部分可以监听这些事件,并在配置发生变化时做出相应的响应,比如清除缓存、重新加载配置等。
- 工厂方法模式(Factory Method): 虽然这段代码直接没有显式展示,但在Spring框架的背景下,SpringApplicationBuilder的使用涉及工厂方法模式。SpringApplicationBuilder用于创建和配置Spring应用的上下文,这里可以看作是用工厂方法来创建ConfigurableApplicationContext的实例。
- 建造者模式(Builder): SpringApplicationBuilder的使用也体现了建造者模式。这个类提供了一系列的链式方法调用来配置和构建Spring应用的上下文。这样的接口提供了一种清晰和灵活的方式来构建复杂对象。
- 单例模式(Singleton): 虽然这段代码中没有直接展示,但在整个Spring框架中,单例模式是默认的作用域。这意味着Spring容器内的bean默认是单例的,保证了在一个应用上下文中只有一个实例。
- ConfigurationProperties动态绑定:ConfigurationPropertiesRebinder监听EnvironmentChangeEvent,当配置变化时,重新绑定标有@ConfigurationProperties注解的Bean,使其加载最新的配置。
性能改进-add改进
updateEnvironment()和addConfigFilesToEnvironment()两个方法都涉及到Spring应用上下文中环境配置的更新,但它们的实现细节和目的有所不同。下面分别比较这两个方法的关键差异:
updateEnvironment()
- 环境处理流程: updateEnvironment()方法首先复制当前环境(StandardEnvironment),然后通过一系列的EnvironmentPostProcessor来重新处理这个环境。这些处理器可以修改环境属性,添加新的属性源等。
- 引导上下文和日志工厂: 在处理环境时,这个方法创建并使用了ConfigurableBootstrapContext和PassthruDeferredLogFactory。这些都是为了支持在应用的引导阶段提供更灵活的日志管理和上下文配置。
- 动态加载和执行环境处理器: 使用SpringFactoriesLoader动态加载所有EnvironmentPostProcessor的实现,然后实例化并执行它们,以对环境进行额外的配置或修改。
- 属性源的合并和排序: 最后,方法将修改后的环境属性源合并回原来的应用上下文中,保持了属性源之间的相对顺序。
addConfigFilesToEnvironment()
- 环境的重新构建: 与updateEnvironment()相比,addConfigFilesToEnvironment()更侧重于通过新建一个Spring应用实例来加载和应用配置文件。这一过程涉及到更多的初始化和引导监听器。
- 专注于配置文件: 该方法通过ConfigFileApplicationListener和BootstrapApplicationListener专门处理Spring配置文件,而不是对环境进行广泛的后处理。
- 环境属性的简单合并: 方法结束时,它将新应用上下文中的环境属性源合并到原始应用上下文的环境中,类似于updateEnvironment(),但过程似乎更直接关联到配置文件的处理。
- 临时应用上下文的管理: 创建了一个新的ConfigurableApplicationContext,用于加载新的配置文件,一旦完成,该临时上下文就会被关闭,这有助于资源的清理和管理。
主要区别
- 目标与重点: updateEnvironment()更多关注于通过执行一系列环境处理器来动态更新环境配置,而addConfigFilesToEnvironment()则重点在于通过创建新的应用上下文来加载和应用新的配置文件。
- 处理机制: updateEnvironment()利用了环境后处理器来修改环境,提供了更细粒度的环境调整能力;而addConfigFilesToEnvironment()则通过启动一个完整的Spring应用上下文来重新加载配置文件,更倾向于全面地重新加载配置。
- 使用场景: updateEnvironment()可能更适用于需要动态调整多个配置源或执行复杂环境调整的场景,而addConfigFilesToEnvironment()可能更适用于简单的配置文件重新加载和应用启动时的配置处理。
- 资源管理: addConfigFilesToEnvironment()需要处理新创建的应用上下文的生命周期,包括正确关闭它来释放资源;而updateEnvironment()主要在现有的上下文内操作,不涉及创建新的应用上下文。