首页 > 其他分享 >Spring Cloud Nacos(一)

Spring Cloud Nacos(一)

时间:2022-10-16 11:22:08浏览次数:43  
标签:dataId group String Spring Nacos fileExtension 加载 Cloud name

PropertySourceLocator加载原理

SpringApplication.run

在spring boot项目启动时,有一个prepareContext的方法,它会回调所有实现了ApplicationContextInitializer的实例,来做一些初始化工作。

public ConfigurableApplicationContext run(String... args) {
	//省略代码...
	prepareContext(context, environment, listeners, applicationArguments, printedBanner);
	//省略代码
	return context; 
}

PropertySourceBootstrapConfiguration.initialize

其中,PropertySourceBootstrapConfiguration就实现了ApplicationContextInitializer,initialize方法代码如下。

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    List<PropertySource<?>> composite = new ArrayList<>();
    //对propertySourceLocators数组进行排序,根据默认的AnnotationAwareOrderComparator
    AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
    boolean empty = true;
    //获取运行的环境上下文
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    for (PropertySourceLocator locator : this.propertySourceLocators) {
        //回调所有实现PropertySourceLocator接口实例的locate方法,并收集到source这个集合中。
        Collection<PropertySource<?>> source = locator.locateCollection(environment);
         //如果source为空,直接进入下一次循环
        if (source == null || source.size() == 0) {
            continue;
        }
        //遍历source,把PropertySource包装成BootstrapPropertySource加入到sourceList中。
        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));
            }
        }
        logger.info("Located property source: " + sourceList);
        //将source添加到数组
        composite.addAll(sourceList);
        //表示propertysource不为空
        empty = false;
    }
    //只有propertysource不为空的情况,才会设置到environment中
    if (!empty) {
        //获取当前Environment中的所有PropertySources.
        MutablePropertySources propertySources = environment.getPropertySources();
        String logConfig = environment.resolvePlaceholders("${logging.config:}");
        LogFile logFile = LogFile.get(environment);
        // 遍历移除bootstrapProperty的相关属性
        for (PropertySource<?> p : environment.getPropertySources()) {
            if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                propertySources.remove(p.getName());
            }
        }
        //把前面获取到的PropertySource,插入到Environment中的PropertySources中。
        insertPropertySources(propertySources, composite);
        reinitializeLoggingSystem(environment, logConfig, logFile);
        setLogLevels(applicationContext, environment);
        handleIncludedProfiles(environment);
    }
}

上述代码逻辑说明如下。

  1. 首先this.propertySourceLocators,表示所有实现了PropertySourceLocators接口的实现类

  2. 根据默认的 AnnotationAwareOrderComparator 排序规则对propertySourceLocators数组进行排序。

  3. 获取运行的环境上下文ConfigurableEnvironment

  4. 遍历propertySourceLocators时调用

    • locate 方法,传入获取的上下文environment
    • 将source添加到PropertySource的链表中
    • 设置source是否为空的标识标量empty
  5. source不为空的情况,才会设置到environment中

    • 返回Environment的可变形式,可进行的操作如addFirst、addLast

    • 移除propertySources中的bootstrapProperties

    • 根据config server覆写的规则,设置propertySources

    • 处理多个active profiles的配置信息

PS:注意:this.propertySourceLocators这个集合中的PropertySourceLocator,是通过自动装配机制完成注入的,具体的实现在BootstrapImportSelector这个类中。

ApplicationContextInitializer的理解和使用

ApplicationContextInitializer是Spring框架原有的东西, 它的主要作用就是在,ConfigurableApplicationContext

类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfiurableApplicationContext的实例做进

一步的设置和处理。

它可以用在需要对应用程序上下文进行编程初始化的web应用程序中,比如根据上下文环境来注册

propertySource,或者配置文件。而Config 的这个配置中心的需求恰好需要这样一个机制来完成。

创建一个TestApplicationContextInitializer

public class TestApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>{
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        ConfigurableEnvironment ce=applicationContext.getEnvironment();
        for(PropertySource<?> propertySource:ce.getPropertySources()){
            System.out.println(propertySource);
         }
		System.out.println("--------end");
 	}
}

添加spi加载

org.springframework.context.ApplicationContextInitializer= \
 com.example.demo.controller.TestApplicationContextInitializer

在控制台就可以看到当前的PropertySource的输出结果。

ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
StubPropertySource {name='servletConfigInitParams'}
StubPropertySource {name='servletContextInitParams'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
MapPropertySource {name='springCloudClientHostInfo'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/bootstrap.yaml]'}
MapPropertySource {name='springCloudDefaultProperties'}
CachedRandomPropertySource {name='cachedrandom'}

NacosPropertySourceLocator

那么如何从远程服务器上加载配置到Spring的Environment中。

PropertySourceLocator的实现类,NacosPropertySourceLocator .

于是,直接来看NacosPropertySourceLocator中的locate方法,代码如下。

public PropertySource<?> locate(Environment env) {
    this.nacosConfigProperties.setEnvironment(env);
    ConfigService configService = this.nacosConfigManager.getConfigService();
    if (null == configService) {
        log.warn("no instance of config service found, can't load config from nacos");
        return null;
    } else {
        //获取客户端配置的超时时间
        long timeout = (long)this.nacosConfigProperties.getTimeout();
        this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
        //获取name属性
        String name = this.nacosConfigProperties.getName();
        //在Spring Cloud中,默认的name=spring.application.name。
        String dataIdPrefix = this.nacosConfigProperties.getPrefix();
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = name;
        }

        if (StringUtils.isEmpty(dataIdPrefix)) {
            //获取spring.application.name,赋值给dataIdPrefix
            dataIdPrefix = env.getProperty("spring.application.name");
        }
		//创建一个Composite属性源,可以包含多个PropertySource
        CompositePropertySource composite = new CompositePropertySource("NACOS");
        this.loadSharedConfiguration(composite);
        //加载扩展配置
        this.loadExtConfiguration(composite);
        //加载自身配置
        this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env);
        return composite;
    }
}

上述代码的实现

  1. 获取nacos客户端的配置属性,并生成dataId(这个很重要,要定位nacos的配置)

  2. 分别调用三个方法从加载配置属性源,保存到composite组合属性源中

loadApplicationConfiguration

暂且先不管加载共享配置、扩展配置的方法,最终本质上都是去远程服务上读取配置,只是传入的参数不一样。

  • fileExtension,表示配置文件的扩展名

  • nacosGroup表示分组

  • 加载dataid=项目名称的配置

  • 加载dataid=项目名称+扩展名的配置

  • 遍历当前配置的激活点(profile),分别循环加载带有profile的dataid配置

private void loadApplicationConfiguration(
    CompositePropertySource compositePropertySource, String dataIdPrefix,
    NacosConfigProperties properties, Environment environment) {
    //默认的扩展名为: properties
    String fileExtension = properties.getFileExtension();
    //获取group
    String nacosGroup = properties.getGroup();
    //加载`dataid=项目名称`的配置
    loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
                           fileExtension, true);
    //加载`dataid=项目名称+扩展名`的配置
    loadNacosDataIfPresent(compositePropertySource,
                           dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
    // 遍历profile(可以有多个),根据profile加载配置
    for (String profile : environment.getActiveProfiles()) {
        //此时的dataId=${spring.application.name}-${profile}.${fileExtension}
        String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
        loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
                               fileExtension, true);
    }

}

loadNacosDataIfPresent

调用loadNacosPropertySource加载存在的配置信息。

把加载之后的配置属性保存到CompositePropertySource中。

private void loadNacosDataIfPresent(final CompositePropertySource composite,
                                    final String dataId, final String group, String fileExtension, boolean isRefreshable) {
    //如果dataId为空,或者group为空,则直接跳过
    if (null == dataId || dataId.trim().length() < 1) {
        return;
    }
    if (null == group || group.trim().length() < 1) {
        return;
    }
    //从nacos中获取属性源
    NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group, fileExtension, isRefreshable);
    //把属性源保存到compositePropertySource中
    this.addFirstPropertySource(composite, propertySource, false);
}

loadNacosPropertySource

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);
}

NacosPropertySourceBuilder.build

NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
    //调用loadNacosData加载远程数据
    List<PropertySource<?>> propertySources = loadNacosData(dataId, group,fileExtension);
    //构造NacosPropertySource(这个是Nacos自定义扩展的PropertySource)
    //相当于把从远程服务器获取的数据保存到NacosPropertySource中。
    NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources, group, dataId, new Date(), isRefreshable);
    //把属性缓存到本地缓存       				
    NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
    return nacosPropertySource;
}

NacosPropertySourceBuilder.loadNacosData

这个方法,就是连接远程服务器去获取配置数据的实现,关键代码是 configService.getConfig

private List<PropertySource<?>> loadNacosData(String dataId, String group,
			String fileExtension) {
		String data = null;
		try {
			data = configService.getConfig(dataId, group, timeout);
			if (StringUtils.isEmpty(data)) {
				log.warn(
						"Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
						dataId, group);
				return Collections.emptyList();
			}
			if (log.isDebugEnabled()) {
				log.debug(String.format(
						"Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
						group, data));
			}
            //对加载的数据进行解析,保存到List<PropertySource>集合。
			return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
					fileExtension);
		}
		catch (NacosException e) {
			log.error("get data from Nacos error,dataId:{} ", dataId, e);
		}
		catch (Exception e) {
			log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
		}
		return Collections.emptyList();
}

总结

通过上述分析,我们知道了Spring Cloud集成Nacos时的关键路径,并且知道在启动时, Spring Cloud会从Nacos Server中加载动态数据保存到Environment集合。

从而实现动态配置的自动注入。

标签:dataId,group,String,Spring,Nacos,fileExtension,加载,Cloud,name
From: https://www.cnblogs.com/snail-gao/p/16795821.html

相关文章

  • Spring boot JDBC
    引入包 yml配置      @Repository表求该类是持久层依赖注入的对象    queryForObject查询单一一条记录。query查询多条记录  服务后端......
  • 使用 Azure SNAT 为 SAP Commerce Cloud 的 outbound connection 进行端口映射
    某些场景需要虚拟机或计算实例具有到Internet的出站连接,比如SAPCommerceCloud部署在CCV2上,公共负载均衡器的前端IP可用于为后端实例提供到Internet的出站连接......
  • SpringBean的生命周期
    SpringBean的生命周期SpringBean的生命周期大体如下:Instantiation:实例化bean(完成构造器注入)依赖注入:属性(接口)注入,setter注入aware:beanName,beanFactory,app......
  • 致 UCloud 的一封感谢信
    故事要从社区服务器的几起故障说起。【1-问题来源】某天,微信群、公众号等几个渠道中,有用户反馈Jenkins中文社区提供的插件更新中心国内镜像源无法访问。【2-故障原因】从......
  • 83-springboot 多模块打包成jar
    主项目:<packaging>pom</packaging>打包配置: <!--指定使用maven打包--> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <arti......
  • 一篇文章带你掌握主流办公框架——SpringBoot
    一篇文章带你掌握主流办公框架——SpringBoot在之前的文章中我们已经学习了SSM的全部内容以及相关整合SSM是Spring的产品,主要用来简化开发,但我们现在所介绍的这款框架—......
  • 结合springboot条件注入@ConditionalOnProperty以及@ConfigurationProperties来重构优
    @ConditionalOnProperty实现按需注入bean短信工具类SmsUtilzhenghe-common是一个基础包。SmsUtil坐落在zhenghe-common里。先看看Smsutil的面目。packagecom.emax......
  • Spring的同一个服务为什么会加载多次?
    问题现象最近在本地调试公司的一个Web项目时,无意中发现日志中出现了两次同一个服务的init记录,项目都是基于Spring来搭建的,按理说服务都是单例的,应该只有一次服务加载日志......
  • Nacos安装指南
    1.Windows安装开发阶段采用单机安装即可。1.1.下载安装包在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:GitHub主页:https://github.com/ali......
  • SpringBoot Logback统一日志处理
    一、日志 1、配置日志级别日志记录器(Logger)的行为是分等级的。如下表所示:分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL默认情况下,springboot从控制台打印出来的......