本篇文章是探究,springboot启动时nacos是如何将配置中心的配置读取到springboot环境中的
org.springframework.cloud.bootstrap.config.PropertySourceLocator 是 springcloud 定义的一个顶级接口,用来定义所有实现类加载自定义配置的类,数据的调用则是在 PropertySourceBootstrapConfiguration 自动装配中
public interface PropertySourceLocator {
/**
* 创建配置源
*
* @param environment 环境
* @return {@code PropertySource> }
* @date 2023-08-09 11:43:03
*/
PropertySource<?> locate(Environment environment);
default Collection<PropertySource<?>> locateCollection(Environment environment) {
return locateCollection(this, environment);
}
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
Environment environment) {
//调用 locate() 方法构建配置对象
PropertySource<?> propertySource = locator.locate(environment);
//如果为空直接返回空的集合
if (propertySource == null) {
return Collections.emptyList();
}
//判断当前对象是否是 CompositePropertySource 多配置文件对象聚合类,这是nacos的默认实现类,会构建多个配置对象
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);
}
}
}
配置属性的全局自动配置类,由 springcloud 定义并且在 springboot初始化时进行调用;配置类中的属性就注入了所有的 PropertySourceLocator接口的实现类
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
/**
* 自定注入所有的实现类 PropertySourceLocator
*/
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
}
initialize 方法在springboot调用 ApplicationContextInitializer 是进行执行
public void initialize(ConfigurableApplicationContext applicationContext) {
//将nacos中的配置构建为 PropertySource 配置属性对象
List<PropertySource<?>> composite = new ArrayList<>();
//根据@Order注解进行排序
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
//获取到环境对象
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//循环遍历
for (PropertySourceLocator locator : this.propertySourceLocators) {
//调用实现方法进行构建配置对象 PropertySource
Collection<PropertySource<?>> source = locator.locateCollection(environment);
//如果配置出来的 PropertySource 为空,那么直接跳过到下一个
if (source == null || source.size() == 0) {
continue;
}
List<PropertySource<?>> sourceList = new ArrayList<>();
//根据配置出来的 PropertySource 在进行包装,除了 EnumerablePropertySource枚举类型配置源,其他的都是 SimpleBootstrapPropertySource
for (PropertySource<?> p : source) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
sourceList.add(new BootstrapPropertySource<>(enumerable));
}
else {
sourceList.add(new SimpleBootstrapPropertySource(p));
}
}
logger.info("Located property source: " + sourceList);
composite.addAll(sourceList);
empty = false;
}
//如果配置不为空的话
if (!empty) {
//获取到环境中的属性配置类,MutablePropertySources 这个类中包含了一个 List>
MutablePropertySources propertySources = environment.getPropertySources();
//解析日志配置的占位符
String logConfig = environment.resolvePlaceholders("${logging.config:}");
//解析日志文件出来
LogFile logFile = LogFile.get(environment);
//移除掉 bootstrapProperties 这个配置源,因为这个配置源一般在容器启动之前就已经全局加载了,不需要再使用
for (PropertySource<?> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
}
//将构建出来的配置源添加到环境中去
insertPropertySources(propertySources, composite);
//再次构建日志系统相关的配置
reinitializeLoggingSystem(environment, logConfig, logFile);
//设置日志级别
setLogLevels(applicationContext, environment);
//这里处理需要包含的环境配置,将配置文件中存在 spring.profiles.include 设置到环境中
handleIncludedProfiles(environment);
}
}
nacos实现的配置定位器,包含的属性值
public class NacosPropertySourceLocator implements PropertySourceLocator {
private static final String NACOS_PROPERTY_SOURCE_NAME = "NACOS";
//分隔符
private static final String DOT = ".";
private static final String SEP1 = "-";
//配置的构建器
private NacosPropertySourceBuilder nacosPropertySourceBuilder;
//nacos配置
private NacosConfigProperties nacosConfigProperties;
//nacos配置管理器
private NacosConfigManager nacosConfigManager;
}
实现方法中,创建了一个配置构建器对象 **nacosPropertySourceBuilder **传入 ConfigService 属性,ConfigService,发起请求的就是 ConfigService类
public PropertySource<?> locate(Environment env) {
nacosConfigProperties.setEnvironment(env);
//配置服务(NacosConfigService),比较核心通过http长轮询的方式获取配置,2.0之后改为grpc
ConfigService configService = nacosConfigManager.getConfigService();
if (null == configService) {
log.warn("no instance of config service found, can't load config from nacos");
return null;
}
long timeout = nacosConfigProperties.getTimeout();
//创建一个配置的构建器
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
timeout);
//先获取到配置文件中指定的名称
String name = nacosConfigProperties.getName();
//获取到配置的前缀
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
//如果配置都为空,那么直接获取 spring.application.name 配置的名称
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
//创建一个配置名称为 nacos 的聚合配置属性对象
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
//加载共享配置文件,分组为默认
loadSharedConfiguration(composite);
//加载扩展配置文件
loadExtConfiguration(composite);
//加载配置文件
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
/**
* 根据文件名称和分组加载配置文件,拼接激活的环境后缀
*
* @param compositePropertySource 复合属性来源
* @param dataIdPrefix 数据id前缀
* @param properties 配置属性
* @param environment 环境
* @date 2023-08-09 13:43:32
*/
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
//获取到指定的文件后缀,默认是properties
String fileExtension = properties.getFileExtension();
//获取到配置的分组
String nacosGroup = properties.getGroup();
// 加载没有带后缀的配置文件例如:product 因为这里直接将服务名称作为了dataId
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
fileExtension, true);
// 加载带后缀的配置文件例如:product.properties
loadNacosDataIfPresent(compositePropertySource,
dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
// 根据激活的环境去拼接对应的配置文件名称并且进行加载
for (String profile : environment.getActiveProfiles()) {
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
/**
* 1. 先通过 NacosPropertySourceRepository 判断配置文件是否已经加载了
* 2. 在 nacosPropertySourceBuilder 通过 configService 加载配置文件,加载完成了后会将其注册到 NacosPropertySourceRepository
* 3. 在上下文创建完成后根据配置文件再注册对应的监听器 NacosContextRefresher
*/
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
}
}
/**
* 通过 ConfigService 服务去拉取配置文件,然后在构建为 NacosPropertySource
*
* @param dataId 数据标识
* @param group 组名
* @param fileExtension 文件扩展名
* @param isRefreshable 是否可刷新
* @return {@code NacosPropertySource }
* @date 2023-08-09 13:42:22
*/
private NacosPropertySource loadNacosPropertySource(final String dataId,
final String group, String fileExtension, boolean isRefreshable) {
if (NacosContextRefresher.getRefreshCount() != 0) {
if (!isRefreshable) {
return NacosPropertySourceRepository.getNacosPropertySource(dataId,
group);
}
}
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
nacos的配置服务,用于发送请求跟nacos注册中心进行交互的组件
public class NacosConfigService implements ConfigService {
private static final Logger LOGGER = LogUtils.logger(NacosConfigService.class);
/**
* 2.0版本之后将会删除,2.0之前采用的是http长轮询的方式,2.0之后就采用rpc
*/
@Deprecated
ServerHttpAgent agent = null;
/**
* 客户端工作器,客户端将配置的监听器注册到这里,然后由这里通过线程池进行长轮询监听配置文件的改变
*/
private final ClientWorker worker;
//命名空间
private String namespace;
//配置文件的过滤器链管理器
private final ConfigFilterChainManager configFilterChainManager;
}
public NacosConfigService(Properties properties) throws NacosException {
//验证参数
ValidatorUtils.checkInitParam(properties);
//初始化命名空间的名称
initNamespace(properties);
//通过 ServiceLoader 加载出来的 ConfigFilter 实现类,然后将其添加到 ConfigFilterChainManager 中
this.configFilterChainManager = new ConfigFilterChainManager(properties);
/**
* 启动服务端列表管理器,如果nacos是集群配置,其中会将配置的对应的服务器集群的地址进行解析
*/
ServerListManager serverListManager = new ServerListManager(properties);
//定时每30秒拉取一次服务的列表
serverListManager.start();
/**
* 启动本地配置信息处理器,2.0之前采用的http长轮询的方式进行监听,2.0之后采用rpc的方式进行监听
* 通过一个阻塞队列进行线程的阻塞,默认是5秒钟便会唤醒一次,然后进行一次配置变动的监听更新,如果
* 有监听器进行注册,那么会立即执行一次
*/
this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, properties);
// will be deleted in 2.0 later versions
agent = new ServerHttpAgent(serverListManager);
}
根据对应的配置名称和组名拉取对应的配置信息
/**
* 根据对应的命名空间、dataId、group、超时时间获取配置文件
*
* @param dataId dataId
* @param group group
* @param timeoutMs read timeout
* @return
* @throws NacosException
*/
@Override
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
return getConfigInner(namespace, dataId, group, timeoutMs);
}
添加配置文件变动的监听器
/**
* 添加监听器
*
* @param dataId 数据标识
* @param group 组
* @param listener 侦听器
* @author zhonghaijun
* @date 2023-08-09 13:45:52
*/
@Override
public void addListener(String dataId, String group, Listener listener) throws NacosException {
worker.addTenantListeners(dataId, group, Arrays.asList(listener));
}
手动推送配置
/**
* 发布配置
*
* @param dataId 数据标识
* @param group 组
* @param content 内容
* @return boolean
* @author zhonghaijun
* @date 2023-08-09 14:44:10
*/
@Override
public boolean publishConfig(String dataId, String group, String content) throws NacosException {
return publishConfig(dataId, group, content, ConfigType.getDefaultType().getType());
}
客户端工作器,这个组件专门用于推送配置、监听配置、获取配置等服务
public ClientWorker(final ConfigFilterChainManager configFilterChainManager, ServerListManager serverListManager,
final Properties properties) throws NacosException {
this.configFilterChainManager = configFilterChainManager;
//初始化参数超时时间、配置重试时间、是否开启远程配置同步(开启后在配置配置文件监听时会先去远程服务器上对内容进行同步一次)
init(properties);
//创建rpc客户端
agent = new ConfigRpcTransportClient(properties, serverListManager);
//创建一个定时任务的线程池,提交一个守护线程
ScheduledExecutorService executorService = Executors
.newScheduledThreadPool(ThreadUtils.getSuitableThreadCount(1), r -> {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker");
t.setDaemon(true);
return t;
});
agent.setExecutor(executorService);
/**
* 启动rpc客户端,
* 调用 当期类的 startInternal() 方法,这个方法监听 listenExecutebell阻塞队列的任务
* 在客户端添加配置监听服务时,会向 listenExecutebell阻塞队列中添加一个任务,这个任务会去远程服务器上获取配置内容
*/
agent.start();
}
/**
* 根据命名空间添加一个配置的监听器
*
* @param dataId dataId of data
* @param group group of data
* @param listeners listeners
* @throws NacosException nacos exception
*/
public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners)
throws NacosException {
//如果分组为空,那么就直接以默认的分组进行处理
group = blank2defaultGroup(group);
//获取到对应的租户信息,就是namespace命名空间
String tenant = agent.getTenant();
//创建缓存数据,如果开启了 enableRemoteSyncConfig,会去远程服务拉取配置信息
CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
synchronized (cache) {
//添加监听器
for (Listener listener : listeners) {
cache.addListener(listener);
}
cache.setSyncWithServer(false);
//向队列listenExecutebell中添加一个元素,用于唤醒监听线程,在startInternal() 方法中进行监听
agent.notifyListenConfig();
}
}