传诚信网站建设官网关键词优化价格
Nacos配置文件读取
本篇文章是探究,springboot启动时nacos是如何将配置中心的配置读取到springboot环境中的
PropertySourceLocator
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);}}}
PropertySourceBootstrapConfiguration
配置属性的全局自动配置类,由 springcloud 定义并且在 springboot初始化时进行调用;配置类中的属性就注入了所有的 PropertySourceLocator接口的实现类
public class PropertySourceBootstrapConfiguration implementsApplicationContextInitializer<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) {//调用实现方法进行构建配置对象 PropertySourceCollection<PropertySource<?>> source = locator.locateCollection(environment);//如果配置出来的 PropertySource 为空,那么直接跳过到下一个if (source == null || source.size() == 0) {continue;}List<PropertySource<?>> sourceList = new ArrayList<>();//根据配置出来的 PropertySource 在进行包装,除了 EnumerablePropertySource枚举类型配置源,其他的都是 SimpleBootstrapPropertySourcefor (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<PropertySource<?>>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);}}
NacosPropertySourceLocator
nacos实现的配置定位器,包含的属性值
- nacosPropertySourceBuilder:nacos的配置属性构建器,就是它去拉取配置然后构建 PropertySource
- nacosConfigProperties:nacos的配置属性类
- nacosConfigManager: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之后改为grpcConfigService 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) {//获取到指定的文件后缀,默认是propertiesString fileExtension = properties.getFileExtension();//获取到配置的分组String nacosGroup = properties.getGroup();// 加载没有带后缀的配置文件例如:product 因为这里直接将服务名称作为了dataIdloadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,fileExtension, true);// 加载带后缀的配置文件例如:product.propertiesloadNacosDataIfPresent(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);}
NacosConfigService
nacos的配置服务,用于发送请求跟nacos注册中心进行交互的组件
public class NacosConfigService implements ConfigService {private static final Logger LOGGER = LogUtils.logger(NacosConfigService.class);/*** 2.0版本之后将会删除,2.0之前采用的是http长轮询的方式,2.0之后就采用rpc*/@DeprecatedServerHttpAgent agent = null;/*** 客户端工作器,客户端将配置的监听器注册到这里,然后由这里通过线程池进行长轮询监听配置文件的改变*/private final ClientWorker worker;//命名空间private String namespace;//配置文件的过滤器链管理器private final ConfigFilterChainManager configFilterChainManager;}
- serverListManager :先构建服务器列表的管理器
- worker:真正执行配置监听和创建的服务
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 versionsagent = new ServerHttpAgent(serverListManager);}
根据对应的配置名称和组名拉取对应的配置信息
/*** 根据对应的命名空间、dataId、group、超时时间获取配置文件** @param dataId dataId* @param group group* @param timeoutMs read timeout* @return* @throws NacosException*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic boolean publishConfig(String dataId, String group, String content) throws NacosException {return publishConfig(dataId, group, content, ConfigType.getDefaultType().getType());}
ClientWorker
客户端工作器,这个组件专门用于推送配置、监听配置、获取配置等服务
- ConfigRpcTransportClient:用于发起rpc的客户端,start() 方法将会回调到 ClientWorker.startInternal() 方法中
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();}}