(1). 概述
与Spring整合时,只需要添加依赖即可,那么,Nacos是如何与Spring整合的呢?答案就在:spring-cloud-starter-alibaba-nacos-config-xxxx.jar包里.
(2). /META-INF/spring.factories
# 1. NacosConfigBootstrapConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
# 2. NacosConfigAutoConfiguration
# NacosConfigEndpointAutoConfiguration在这里了不参与剖析
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
(3). NacosConfigBootstrapConfiguration
@Configuration
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {
// 1. 构建出业务模型所需要的配置信息
@Bean
@ConditionalOnMissingBean
public NacosConfigProperties nacosConfigProperties() {
return new NacosConfigProperties();
}
// 2. 构建:NacosConfigManager
@Bean
@ConditionalOnMissingBean
public NacosConfigManager nacosConfigManager(
// 依赖:NacosConfigProperties
NacosConfigProperties nacosConfigProperties) {
return new NacosConfigManager(nacosConfigProperties);
}
// ****************************************************************************************
// 3. 构建:NacosPropertySourceLocator
// Spring启动时,会回调该方法:PropertySource<?> NacosPropertySourceLocator.locate(Environment environment);
// ****************************************************************************************
@Bean
public NacosPropertySourceLocator nacosPropertySourceLocator(
// 依赖NacosConfigManager
NacosConfigManager nacosConfigManager) {
return new NacosPropertySourceLocator(nacosConfigManager);
}
}
(4). NacosConfigAutoConfiguration
@Configuration
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigAutoConfiguration {
// 1. NacosRefreshHistory(动态更新配置的记录)
@Bean
public NacosRefreshHistory nacosRefreshHistory() {
return new NacosRefreshHistory();
}
// 2. NacosRefreshProperties(动态更新配置是否启用的配置项)
@Bean
public NacosRefreshProperties nacosRefreshProperties() {
return new NacosRefreshProperties();
}
// *******************************************************************
// 3. NacosContextRefresher.registerNacosListener方法会触发:RefreshEvent事件.
// applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));
// *******************************************************************
@Bean
public NacosContextRefresher nacosContextRefresher(
NacosConfigManager nacosConfigManager,
NacosRefreshHistory nacosRefreshHistory) {
return new NacosContextRefresher(nacosConfigManager, nacosRefreshHistory);
}
}
(5). NacosConfigManager
NacosConfigManager的作用:
- 转换NacosConfigProperties为Properties.
- NacosFactory.createConfigService(Properties)创建:ConfigService.
public class NacosConfigManager {
private static final Logger log = LoggerFactory.getLogger(NacosConfigManager.class);
private static ConfigService service = null;
private NacosConfigProperties nacosConfigProperties;
// *****************************************************
// 1. 构造器
// *****************************************************
public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {
this.nacosConfigProperties = nacosConfigProperties;
// *****************************************************
// 2. 创建:ConfigService
// *****************************************************
createConfigService(nacosConfigProperties);
}
static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {
if (Objects.isNull(service)) {
// 单例模式
synchronized (NacosConfigManager.class) {
try {
if (Objects.isNull(service)) {
// ******************************************************************
// 3. 收集:NacosConfigProperties里的信息,并转换成:Properties
// 这个方法是,Nacos的入口方法,并不陌生吧! NacosFactory.createConfigService(Properties)
// ******************************************************************
service = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());
}
}
catch (NacosException e) {
log.error(e.getMessage());
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;
}
}
(6). NacosContextRefresher
NacosContextRefresher的作用:
- NacosPropertySource属于:PropertySource的实现,它是数据的载体.
- 获得系统中所有的:NacosPropertySource.
- 为ConfigService配置Listener,Listener内部会触发RefreshEvent事件.
public class NacosContextRefresher
// ApplicationReadyEvent是Spring启动时的事件.
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
private NacosConfigProperties nacosConfigProperties;
private final boolean isRefreshEnabled;
private final NacosRefreshHistory nacosRefreshHistory;
// *******************************************************************
// 不陌生吧,ConfigService是Nacos的入口程序.
// *******************************************************************
private final ConfigService configService;
private ApplicationContext applicationContext;
private AtomicBoolean ready = new AtomicBoolean(false);
private Map<String, Listener> listenerMap = new ConcurrentHashMap<>(16);
// 1. 构造器
public NacosContextRefresher(
// 依赖:NacosConfigManager
NacosConfigManager nacosConfigManager,
NacosRefreshHistory refreshHistory) {
this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
this.nacosRefreshHistory = refreshHistory;
this.configService = nacosConfigManager.getConfigService();
this.isRefreshEnabled = this.nacosConfigProperties.isRefreshEnabled();
}
// 2. Spring回调:ApplicationContextAware.setApplicationContext
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// 3. Spring启动,触发:ApplicationReadyEvent时,会回调该方法.
public void onApplicationEvent(ApplicationReadyEvent event) {
if (this.ready.compareAndSet(false, true)) { //ready是元子性的操作,保证只调用该方法一次
this.registerNacosListenersForApplications();
}
}// end onApplicationEvent
private void registerNacosListenersForApplications() {
if (isRefreshEnabled()) {
// *****************************************************************
// 4. NacosPropertySource属于:PropertySource的实现,它是数据的载体.
// *****************************************************************
for (NacosPropertySource propertySource : NacosPropertySourceRepository.getAll()) {
if (!propertySource.isRefreshable()) { // 不支持刷新的情况下,跳过
continue;
}
// 取出:dataId和group
String dataId = propertySource.getDataId();
// 5. 调用:registerNacosListener方法,为:ConfigService配置:Listener
registerNacosListener(propertySource.getGroup(), dataId);
}
}
}
private void registerNacosListener(final String groupKey, final String dataKey) {
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
// 为key,创建监听器
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
// todo feature: support single refresh for listening
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug(String.format(
"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
group, dataId, configInfo));
}
}
});
try {
// ****************************************************************
// 6.为ConfigService配置监听器
// ****************************************************************
configService.addListener(dataKey, groupKey, listener);
}
catch (NacosException e) {
log.warn(String.format(
"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
groupKey), e);
}
}
}
(7). 总结
1) 通过NacosConfigManager创建ConfigService.
2) NacosPropertySourceLocator.locate方法,会返回PropertySource,Spring会把PropertySource添加到:Environment中.
3) 为ConfigService配置监听器,当监听器触发时,触发:RefreshEvent事件,刷新Spring中的配置.