前言
本章学习spring-cloud-starter-alibaba-nacos-config如何集成nacos。
- 配置如何注入Spring容器
- 配置如何动态更新
注:截至目前官方spring-cloud-starter-alibaba-nacos-config仅支持nacos-client版本到1.4.x。
一、注入了哪些Bean
从spring.factories看,spring-cloud-starter-alibaba-nacos-config主要加载了两个重要的自动配置:
- NacosConfigBootstrapConfiguration:Bootstrap容器自动配置。
- NacosConfigAutoConfiguration:Application容器自动配置。
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer
org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
Boostrap容器
NacosConfigBootstrapConfiguration在Bootstrap容器中注入了三个Bean。
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {
// spring.cloud.nacos.config配置
@Bean
@ConditionalOnMissingBean
public NacosConfigProperties nacosConfigProperties() {
return new NacosConfigProperties();
}
// 管理ConfigService
@Bean
@ConditionalOnMissingBean
public NacosConfigManager nacosConfigManager(
NacosConfigProperties nacosConfigProperties) {
return new NacosConfigManager(nacosConfigProperties);
}
// PropertySourceBootstrapConfiguration会加载NacosPropertySourceLocator提供的配置
@Bean
public NacosPropertySourceLocator nacosPropertySourceLocator(
NacosConfigManager nacosConfigManager) {
return new NacosPropertySourceLocator(nacosConfigManager);
}
}
1、NacosConfigProperties
使用nacos时,bootstrap.yml配置如下:
spring:
application:
name: nacos-config-example
profiles:
active: DEV
---
spring:
profiles: DEV
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: 789b5be0-0286-4cda-ac0c-e63f5bae3652
group: DEFAULT_GROUP
extension-configs:
- data_id: arch.properties
group: arch
refresh: true
- data_id: jdbc.properties
group: data
refresh: false
shared-configs:
- data_id: share.properties
group: DEFAULT_GROUP
refresh: true
NacosConfigProperties存储了上述配置。
@ConfigurationProperties(NacosConfigProperties.PREFIX)
public class NacosConfigProperties {
public static final String PREFIX = "spring.cloud.nacos.config";
private String serverAddr;
// 命名空间
private String namespace;
// 分组
private String group = "DEFAULT_GROUP";
// dataId前缀 name > prefix > spring.application.name
private String prefix;
private String name;
// 配置文件扩展名
private String fileExtension = "properties";
/**
* a set of shared configurations .e.g:
* spring.cloud.nacos.config.shared-configs[0]=xxx .
*/
private List<Config> sharedConfigs;
/**
* a set of extensional configurations .e.g:
* spring.cloud.nacos.config.extension-configs[0]=xxx .
*/
private List<Config> extensionConfigs;
/**
* the master switch for refresh configuration, it default opened(true).
*/
private boolean refreshEnabled = true;
public static class Config {
private String dataId;
private String group = "DEFAULT_GROUP";
private boolean refresh = false;
}
}
- application配置:对应Nacos的dataId = {prefix}-{spring.profiles.active}.{file-extension}。对于prefix前缀,优先级prefix > name > spring.application.name。应用配置内部也有优先级,从低到高:
- {prefix}
- {prefix}-{spring.profiles.active}
- {prefix}-{spring.profiles.active}.{file-extension}
- sharedConfigs:共享配置。
- extensionConfigs:扩展配置。
- 三种配置优先级:share < extension < application
- 配置刷新:NacosConfigProperties.refreshEnabled总控配置是否可以刷新,shareConfigs和extensionConfigs内部可以定义自己的刷新机制。
2、NacosConfigManager
NacosConfigManager负责管理ConfigService,Bootstrap和Application容器共享一个ConfigService(单例)。
public class NacosConfigManager {
// 单例ConfigService
private static ConfigService service = null;
private NacosConfigProperties nacosConfigProperties;
public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {
this.nacosConfigProperties = nacosConfigProperties;
createConfigService(nacosConfigProperties);
}
static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {
if (Objects.isNull(service)) {
synchronized (NacosConfigManager.class) {
try {
if (Objects.isNull(service)) {
service = NacosFactory.createConfigService(
nacosConfigProperties.assembleConfigServiceProperties());
}
}
catch (NacosException e) {
throw new NacosConnectionFailureException(
nacosConfigProperties.getServerAddr(), e.getMessage(), e);
}
}
}
return service;
}
public ConfigService getConfigService() {
if (Objects.isNull(service)) {
createConfigService(this.nacosConfigProperties);
}
return service;
}
public NacosConfigProperties getNacosConfigProperties() {
return nacosConfigProperties;
}
}
3、NacosPropertySourceLocator
NacosPropertySourceLocator实现了Spring的PropertySourceLocator接口。
public class NacosPropertySourceLocator implements PropertySourceLocator {
}
PropertySourceLocator的作用是提供PropertySource,需要用户实现locate方法。
public interface PropertySourceLocator {
PropertySource<?> locate(Environment environment);
default Collection<PropertySource<?>> locateCollection(Environment environment) {
return locateCollection(this, environment);
}
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
Environment environment) {
PropertySource<?> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
}
if (CompositePropertySource.class.isInstance(propertySource)) {
Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource)
.getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList<>();
for (PropertySource<?> p : sources) {
if (p != null) {
filteredSources.add(p);
}
}
return filteredSources;
}
else {
return Arrays.asList(propertySource);
}
}
}
Bootstrap自动配置只是注入了上述三个Bean,并没有实际接入配置中心。实际接入Nacos配置发生在Application容器中。
Application容器
NacosConfigAutoConfiguration向Application容器中注入了4个Bean。其中NacosConfigProperties直接取了父容器中的实例;NacosConfigManager实例与父容器实例不同,但是内部使用的static ConfigService与父容器中的实例是同一个实例。
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigAutoConfiguration {
@Bean
public NacosConfigProperties nacosConfigProperties(ApplicationContext context) {
// 使用bootstrap父容器中的NacosConfigProperties
if (context.getParent() != null
&& BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
context.getParent(), NacosConfigProperties.class).length > 0) {
return BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(),
NacosConfigProperties.class);
}
return new NacosConfigProperties();
}
// 基于内存的配置刷新记录
@Bean
public NacosRefreshHistory nacosRefreshHistory() {
return new NacosRefreshHistory();
}
// 管理ConfigService,内部使用的ConfigService是Bootstrap容器中的
@Bean
public NacosConfigManager nacosConfigManager(
NacosConfigProperties nacosConfigProperties) {
return new NacosConfigManager(nacosConfigProperties);
}
// 配置刷新
@Bean
public NacosContextRefresher nacosContextRefresher(
NacosConfigManager nacosConfigManager,
NacosRefreshHistory nacosRefreshHistory) {
return new NacosContextRefresher(nacosConfigManager, nacosRefreshHistory);
}
}
1、NacosRefreshHistory
NacosRefreshHistory负责记录近20次配置刷新记录。
public class NacosRefreshHistory {
private static final int MAX_SIZE = 20;
private final LinkedList<Record> records = new LinkedList<>();
private final ThreadLocal<DateFormat> DATE_FORMAT = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
private MessageDigest md;
public NacosRefreshHistory() {
try {
md = MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e) {
log.error("failed to initialize MessageDigest : ", e);
}
}
public void addRefreshRecord(String dataId, String group, String data) {
records.addFirst(new Record(DATE_FORMAT.get().format(new Date()), dataId, group,
md5(data), null));
// 保留最近20条
if (records.size() > MAX_SIZE) {
records.removeLast();
}
}
public LinkedList<Record> getRecords() {
return records;
}
}
2、NacosContextRefresher
NacosContextRefresher负责向Nacos注册配置监听和处理配置变更,利用SpringBoot的ApplicationReadyEvent事件钩子,触发注册配置监听逻辑。
public class NacosContextRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (this.ready.compareAndSet(false, true)) {
this.registerNacosListenersForApplications();
}
}
}
二、配置注入
如何加载配置分为两个方面:何时注入、如何注入。
何时注入
为了实例化单例Bean时能读到Nacos配置,需要在Bean实例化之前就将配置注入Spring的Environment。
Nacos利用PropertySourceBootstrapConfiguration这个ApplicationContextInitializer,在Application容器刷新前(prepareContext阶段)使用NacosPropertySourceLocator将nacos配置转换为PropertySource注入了Environment。
看看Spring提供的PropertySourceBootstrapConfiguration如何调用自定义PropertySourceLocator,如果以后有类似需求可以参考Nacos的实现方式。
首先spring-cloud-context中的spring.factories将PropertySourceBootstrapConfiguration作为BootstrapConfiguration自动配置,注入了Bootstrap容器。
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
接着,PropertySourceBootstrapConfiguration注入了所有PropertySourceLocator。注意,这里在Bootstrap容器中,父容器对子容器是无感知的,这也是为什么NacosPropertySourceLocator需要在Bootstrap容器中注入。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
// 注入容器中的PropertySourceLocator
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
}
接下来看看PropertySourceBootstrapConfiguration实现ApplicationContextInitializer的逻辑。
// PropertySourceBootstrapConfiguration
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// PropertySourceLocator提供的配置项结果集
List<PropertySource<?>> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 循环所有PropertySourceLocator执行它的locateCollection方法
// 返回的PropertySource放入composite
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection<PropertySource<?>> source = locator.locateCollection(environment);
if (source == null || source.size() == 0) {
continue;
}
List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
sourceList.add(new BootstrapPropertySource<>(enumerable));
}
else {
sourceList.add(new SimpleBootstrapPropertySource(p));
}
}
composite.addAll(sourceList);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
// ...
// 将收集到的composite,插入到environment中的propertySources中
insertPropertySources(propertySources, composite);
// ...
}
}
默认情况下,这些加载的PropertySource优先级会高于其他配置。代码层面的体现就是,所有外部配置会通过environment.propertySources.addFirst加入environment。
private void insertPropertySources(MutablePropertySources propertySources,
List<PropertySource<?>> composite) {
MutablePropertySources incoming = new MutablePropertySources();
List<PropertySource<?>> reversedComposite = new ArrayList<>(composite);
Collections.reverse(reversedComposite);
for (PropertySource<?> p : reversedComposite) {
incoming.addFirst(p);
}
PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
Binder.get(environment(incoming)).bind("spring.cloud.config",
Bindable.ofInstance(remoteProperties));
// #1 会走这environment.propertySources.addFirst
if (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone()
&& remoteProperties.isOverrideSystemProperties())) {
for (PropertySource<?> p : reversedComposite) {
propertySources.addFirst(p);
}
return;
}
// ...
}
Environment会利用PropertySourcesPropertyResolver读取配置,org.springframework.core.env.PropertySourcesPropertyResolver#getProperty是Spring读取配置的入口方法,会循环所有propertySource,排在越前面的优先级越高。
// PropertySourcesPropertyResolver
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
return null;
}
如何注入
NacosPropertySourceLocator的locate方法返回Nacos配置,在PropertySourceBootstrapConfiguration中负责将这些配置注入Environment。
// NacosPropertySourceLocator
@Override
public PropertySource<?> locate(Environment env) {
nacosConfigProperties.setEnvironment(env);
// 1. 获取Nacos核心API ConfigService
ConfigService configService = nacosConfigManager.getConfigService();
long timeout = nacosConfigProperties.getTimeout();
// 2. 构造Nacos配置Builder
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
// 3. dataId前缀按照优先级获取
// prefix > name > spring.application.name
String name = nacosConfigProperties.getName();
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
// 4. 返回配置按照优先级处理 share < extension < application
CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
// share
loadSharedConfiguration(composite);
// extention
loadExtConfiguration(composite);
// application
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
注意这里的两个优先级:
- 应用配置的dataId前缀优先级:spring.cloud.nacos.config.prefix > spring.cloud.nacos.config.name > spring.application.name
- 应用配置、共享配置、扩展配置的优先级:share < extension < application,原因是composite.addFirst,越靠前优先级越高。
读取nacos配置,无非是根据namespace&group&dataId获取配置。这里就关注application配置,share和extention忽略。
// NacosPropertySourceLocator
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
String fileExtension = properties.getFileExtension();
String nacosGroup = properties.getGroup();
// load directly once by default
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
fileExtension, true);
// load with suffix, which have a higher priority than the default
loadNacosDataIfPresent(compositePropertySource,
dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
// Loaded with profile, which have a higher priority than the suffix
for (String profile : environment.getActiveProfiles()) {
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
}
}
应用配置实际上页有三种,优先级从低到高依次是:
- dataIdPrefix
- dataIdPrefix.properties
- dataIdPrefix-profile.properties
NacosPropertySourceLocator只是处理了优先级关系,最终是调用NacosPropertySourceBuilder的build方法返回PropertySource。
// NacosPropertySourceLocator
private void loadNacosDataIfPresent(final CompositePropertySource composite,
final String dataId, final String group, String fileExtension,
boolean isRefreshable) {
// ...
NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
fileExtension, isRefreshable);
this.addFirstPropertySource(composite, propertySource, false);
}
private NacosPropertySource loadNacosPropertySource(final String dataId,
final String group, String fileExtension, boolean isRefreshable) {
// ...
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
NacosPropertySourceBuilder真正调用了ConfigService获取远程配置,并将配置缓存到NacosPropertySourceRepository。
public class NacosPropertySourceBuilder {
private ConfigService configService;
private long timeout;
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
// 1. 调用ConfigService读取远程配置
List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
fileExtension);
// 2. 封装为NacosPropertySource
NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
group, dataId, new Date(), isRefreshable);
// 3. 将配置缓存到NacosPropertySourceRepository
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
// 4. 返回给外部,加入Environment
return nacosPropertySource;
}
private List<PropertySource<?>> loadNacosData(String dataId, String group,
String fileExtension) {
String data = configService.getConfig(dataId, group, timeout);
return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
fileExtension);
}
}
这里稍微关注一下NacosDataParserHandler负责解析ConfigService返回的配置。
public final class NacosDataParserHandler {
private static List<PropertySourceLoader> propertySourceLoaders;
private NacosDataParserHandler() {
propertySourceLoaders = SpringFactoriesLoader
.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
}
public List<PropertySource<?>> parseNacosData(String configName, String configValue,
String extension) throws IOException {
// ...
for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
// 找到可以处理 扩展名配置文件的对应PropertySourceLoader
if (!canLoadFileExtension(propertySourceLoader, extension)) {
continue;
}
// ...
}
return Collections.emptyList();
}
// 返回单例
public static NacosDataParserHandler getInstance() {
return ParserHandler.HANDLER;
}
private static class ParserHandler {
private static final NacosDataParserHandler HANDLER = new NacosDataParserHandler();
}
}
这里关注一下NacosDataParserHandler,是因为这是个spring集成的挺好的案例。往往三方框架会有很多提供给用户的扩展点,比如各种SPI机制。
spring-cloud-starter-alibaba-nacos-config可以让用户实现PropertySourceLoader用于处理不同扩展名的配置文件,只需要用户在自己的spring.factories中添加如下配置:
org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
SpringFactoriesLoader.loadFactories方法,可以读到所有PropertySourceLoader的实现类,用于后续处理配置文件解析。
三、动态更新
NacosContextRefresher负责监听nacos配置变更。
当Spring容器完全启动以后,NacosContextRefresher会收到ApplicationReadyEvent,此时开启监听。
// NacosContextRefresher
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (this.ready.compareAndSet(false, true)) {
this.registerNacosListenersForApplications();
}
}
private void registerNacosListenersForApplications() {
// spring.cloud.nacos.config.isRefreshEnabled总控开关默认开启
if (isRefreshEnabled()) {
// 从缓存中获取所有Nacos配置
for (NacosPropertySource propertySource : NacosPropertySourceRepository.getAll()) {
if (!propertySource.isRefreshable()) {
continue;
}
String dataId = propertySource.getDataId();
// 注册监听
registerNacosListener(propertySource.getGroup(), dataId);
}
}
}
监听逻辑如下,也是调用ConfigService的addListener方法向nacos服务端发起监听请求。这里注意到Listener的实现,是通过RefreshEvent实现的。
// NacosContextRefresher
private void registerNacosListener(final String groupKey, final String dataKey) {
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
}
});
try {
configService.addListener(dataKey, groupKey, listener);
}
catch (NacosException e) {
log.warn(String.format(
"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
groupKey), e);
}
}
也就是说,spring-cloud-starter-alibaba-nacos-config必须配合RefreshScope一同使用才能实现配置动态更新,并且一个配置文件的更新会导致所有RefreshScope里的Bean重新实例化,暂时还不支持单个配置文件的刷新。
// org.springframework.cloud.endpoint.event
public class RefreshEventListener implements SmartApplicationListener {
private ContextRefresher refresh;
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationReadyEvent.class.isAssignableFrom(eventType)
|| RefreshEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
handle((ApplicationReadyEvent) event);
}
else if (event instanceof RefreshEvent) {
handle((RefreshEvent) event);
}
}
public void handle(RefreshEvent event) {
if (this.ready.get()) { // don't handle events before app is ready
Set<String> keys = this.refresh.refresh();
}
}
}
总结
截至目前官方spring-cloud-starter-alibaba-nacos-config仅支持nacos-client版本到1.4.x。
配置注入
在ApplicationContext.prepareContext阶段,NacosPropertySourceLocator将Nacos配置注入了Environment。
NacosPropertySourceLocator读取Nacos配置,底层也是调用了nacos-client的ConfigService里的方法。
配置优先级
利用Spring的CompositePropertySource内部链表结构,越靠前的配置项,优先级越高。
在SpringCloud中Nacos配置分为三类:
- 应用配置:对应Nacos的一个命名空间下一个分组下的dataId。dataId = {prefix}-{spring.profiles.active}.{file-extension}。对于prefix前缀,优先级spring.cloud.nacos.config.prefix > spring.cloud.nacos.config.name > spring.application.name。应用配置内部也有优先级,从低到高:
- {prefix}
- {prefix}-{spring.profiles.active}
- {prefix}-{spring.profiles.active}.{file-extension}
- 扩展配置extension-configs:默认不能刷新。
- 共享配置shared-configs:默认不能刷新。
动态更新
当Spring容器完全启动以后,NacosContextRefresher会收到ApplicationReadyEvent,此时开启监听。
NacosContextRefresher循环所有NacosPropertySource,调用nacos-client的ConfigService.addListener注册监听。
// NacosContextRefresher
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (this.ready.compareAndSet(false, true)) {
this.registerNacosListenersForApplications();
}
}
private void registerNacosListenersForApplications() {
// spring.cloud.nacos.config.isRefreshEnabled总控开关默认开启
if (isRefreshEnabled()) {
// 从缓存中获取所有Nacos配置
for (NacosPropertySource propertySource : NacosPropertySourceRepository.getAll()) {
if (!propertySource.isRefreshable()) {
continue;
}
String dataId = propertySource.getDataId();
// 注册监听
registerNacosListener(propertySource.getGroup(), dataId);
}
}
}
当监听器被触发回调时,会发布RefreshEvent事件,只要单个dataId发生变更,将导致所有RefreshScope里的Bean被销毁并重新创建,暂时不支持单个dataId对应的配置重新注入。
// NacosContextRefresher
private void registerNacosListener(final String groupKey, final String dataKey) {
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
}
});
configService.addListener(dataKey, groupKey, listener);
}