【你好Archaius】十一:Netflix Archaius通过获取级联配置来实现多环境配置

每日一句:

真正的价值并不在人生的舞台上,而在我们扮演的角色中。

前言

前面都是在说Archaius是如何管理配置 配置如何事项动态性的。但是作为一个配置组件 多环境配置是必不可少的。

示例一

在classpath下面定义了如下的4个配置文件。

========application.properties========
person.name=coredy
person.age=29
=======application-dev.properties=====
person.name=dev_coredy
person.age=dev_29
=======application-test.properties=====
person.name=test_coredy
person.age=test_29
=======application-pro.properties=====
person.name=pro_coredy
person.age=pro_29

测试代码

	System.setProperty(DeploymentContext.ContextKey.environment.getKey() , "pro");
    ConfigurationManager.loadCascadedPropertiesFromResources("application");
    String name = ConfigurationManager.getConfigInstance().getString("person.name");
    assertThat(name).isEqualTo("pro_coredy");

上面的demo很简单 在程序初始化的时候设置DeploymentContext.ContextKey.environment.getKey()为生产环境。并且调用loadCascadedPropertiesFromResources来加载application.properties文件。这个方法非常关键!从名字可以看出是级联加载的意思。 这样我们就能轻松的实现 多环境的部署。
但是如果在classpath下面出现了config.properties配置文件 并且里面有相同的属性 就会覆盖application-x.properties配置。 具体原因我们后续分析。

示例二

在classpath下面定义了如下的4个配置文件。

========application.properties========
person.name=coredy
person.age=29
@next=database-${@region}.properties
=======database-dev.properties=====
url=dev-databaseurl
=======database-test.properties=====
url=test-databaseurl
=======database-pro.properties=====
url=pro-databaseurl

测试代码

 	System.setProperty(DeploymentContext.ContextKey.region.getKey() , "dev");
    ConfigurationManager.loadCascadedPropertiesFromResources("application");
    String url = ConfigurationManager.getConfigInstance().getString("url");
    assertThat(url).isEqualTo("dev-databaseurl");

同样也能实现 多环境配置的目的。这里使用了一个@next特定属性来级联加载配置文件。或许就是loadCascadedPropertiesFromResources这个方法名字的含义了。

ConfigurationManager.loadCascadedPropertiesFromResources

可以看到上面的两个例子 都是和loadCascadedPropertiesFromResources这个方法有关。那么一定是这个方法在其中发挥着作用。下面我们就深入这个方法的代码分析一下为什么会有上面的结果
跟进:loadCascadedPropertiesFromResources

        //这里不用说 是加载级联配置文件
  		Properties props = loadCascadedProperties(configName);
  		//下面这里也需要注意一下,instance是一个组合配置 加载回来的配置
  		//会被instance.addConfiguration  意味着什么?意味着我们上面加载回来的配置
  		//是放在instance最后面的 instance是一个组合配置 内部维护了一个Configration列表
  		//那也就是说 优先级是低于 默认配置和系统配置等等。默认配置就是config.peoperties
        if (instance instanceof AggregatedConfiguration) {
            ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
            config.loadProperties(props);
            ((AggregatedConfiguration) instance).addConfiguration(config, configName);
        } else {
            ConfigurationUtils.loadProperties(props, instance);
        }

上面的loadCascadedProperties这个方法是关键 继续进入。

private static Properties loadCascadedProperties(String configName) throws IOException {
        //拼接配置文件名字 所以我们在传参的时候注意不能带有.properties后缀
        String defaultConfigFileName = configName + ".properties";
        //下面如果Configuration
        if (instance == null) {
            instance = getConfigInstance();
        }
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        URL url = loader.getResource(defaultConfigFileName);
        if (url == null) {
            throw new IOException("Cannot locate " + defaultConfigFileName + " as a classpath resource.");
        }
        //加载配置文件注意名字是defaultConfigFileName
        Properties props = getPropertiesFromFile(url);
        //获取部署信息的上下午 这个值是我们设置进去的
        String environment = getDeploymentContext().getDeploymentEnvironment();
        if (environment != null && environment.length() > 0) {
            //如果环境变量不为null 拼接名字 加载属性
            String envConfigFileName = configName + "-" + environment + ".properties";
            url = loader.getResource(envConfigFileName);
            if (url != null) {
                //最终也是调用getPropertiesFromFile来解析配置
                Properties envProps = getPropertiesFromFile(url);
                if (envProps != null) {
                    props.putAll(envProps);
                }
            }
        }
        return props;
    }

到这里 我们示例一中的多环境配置已经一目了然了。就是上面这个方法做了一个environment的取值然后如果不为空 就重新拼接了名字 继续加载配置。那么示例二中的级联配置还是没有体现。下面我们继续进入getPropertiesFromFile 这个方法。

   public static Properties getPropertiesFromFile(URL startingUrl) 
    throws FileNotFoundException {
        return ConfigurationUtils.getPropertiesFromFile(startingUrl, getLoadedPropertiesURLs(), "@next", "netflixconfiguration.properties.nextLoad");
    }

看到这个方法 看到传的参数 其实真相已经大白。下面我们进入ConfigurationUtils.getPropertiesFromFile看一下是怎么处理的。

//这个方法没什么看的 主要的逻辑是交给了getConfigFromPropertiesFile来处理的
//loaded 这个参数是用来保存已经加载了的配置文件的地址 
 public static Properties getPropertiesFromFile(URL startingUrl, Set<String> loaded, String... nextLoadKeys) 
            throws FileNotFoundException {
        AbstractConfiguration config = getConfigFromPropertiesFile(startingUrl, loaded, nextLoadKeys);
        return getProperties(config);
    }

public static AbstractConfiguration getConfigFromPropertiesFile(URL startingUrl, Set<String> loaded, String... nextLoadKeys) 
            throws FileNotFoundException {
        if (loaded.contains(startingUrl.toExternalForm())) {
            logger.warn(startingUrl + " is already loaded");
            return null;
        }
        PropertiesConfiguration propConfig = null;
        try {
        //OverridingPropertiesConfiguration 这个类是ConfigurationUtils的内部类你可以它看成PropertiesConfigration 
        //唯一的区别是 addProperty的时候 如果有相同key的属性  它是先清空 再添加。
            propConfig = new OverridingPropertiesConfiguration(startingUrl);
            logger.info("Loaded properties file " + startingUrl);            
        } catch (ConfigurationException e) {
            Throwable cause = e.getCause();
            if (cause instanceof FileNotFoundException) {
                throw (FileNotFoundException) cause;
            } else {
                throw new RuntimeException(e);
            }
        }
        //如果 @next 没有配置 直接返回主配置文件了
        if (nextLoadKeys == null) {
            return propConfig;
        }
  		//下面两行代码找到主配置文件的 文件路径 
        String urlString = startingUrl.toExternalForm();
        String base = urlString.substring(0, urlString.lastIndexOf("/"));
        //将已经加载的主配置文件加入loaded集合。
        loaded.add(startingUrl.toString());
        //加载@next属性 对应的配置文件
        loadFromPropertiesFile(propConfig, base, loaded, nextLoadKeys);
        return propConfig;
    }

继续跟进loadFromPropertiesFile

static void loadFromPropertiesFile(AbstractConfiguration config, String baseUrl, Set<String> loaded, String... nextLoadKeys) {
		// 找@next对应的值  config:是主配置文件解析出来的 baseUrl是主配置文件目录
        String nextLoad = getNextLoad(config, nextLoadKeys);
        if (nextLoad == null) {
            return;
        }
        //解析出来是多个值
        String[] filesToLoad = nextLoad.split(",");
        //逐个解析
        for (String fileName: filesToLoad) {
            fileName = fileName.trim();
            try {
                URL url = new URL(baseUrl + "/" + fileName);
                // avoid circle
                if (loaded.contains(url.toExternalForm())) {
                    logger.warn(url + " is already loaded");
                    continue;
                }
                //还是一样将解析过的路径加入loaded
                loaded.add(url.toExternalForm());
                //注意这里 实例化的是一个OverridingPropertiesConfiguration 
                //config是主配置文件解析的内容 copyProperties这个方法会覆盖主配置文件的内容 从而子配置文件生效
                //这里其实实例一个PropertiesConfiguration实例也能达到效果。前面说了
                //OverridingPropertiesConfiguration和PropertiesConfiguration唯一的区别就是addProperty方法 前者调用了clear直接清除 属于完全覆盖
                //但是copyProperties方法并没有调用addProperty方法 而是调用setProperty
                PropertiesConfiguration nextConfig = new OverridingPropertiesConfiguration(url);
                copyProperties(nextConfig, config);
                logger.info("Loaded properties file " + url);
                //这里是一个递归调用。因为有可能加载回来的子配置文件里面还有@next配置 所以需要getNextLoad再一次解析
                loadFromPropertiesFile(config, baseUrl, loaded, nextLoadKeys);
            } catch (Throwable e) {
                logger.warn("Unable to load properties file", e);
            }
        }
    }

结束语

流程到这就结束了,通过翻阅源码 解答了我们上面的两个示例工作的原理。其实读到这里 如果翻阅Spring Environment源码的朋友可能会发现 有点相似(如果对SpringEnvironment感兴趣的这里有SpringBoot源码系列:Environment机制深入分析(一) )。或许Spring的Environment就是借鉴Archaius的 ,又或许它借鉴了Spring。不管谁借鉴了谁,这何尝不是我们学习源码的意义。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值