[享学Netflix] 十一、Netflix Archaius配置管理器ConfigurationManager和动态属性支持DynamicPropertySupport

元气小坏坏 提交于 2020-02-24 07:10:36

要相信:你遇到的问题,肯定不止你一个人遇到过。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

上面文章介绍了Netflix ArchaiusCommons Configuration核心API Configuration的扩展实现,知道了Netflix Archaius依赖于Commons Configuration并且在其基础上做了扩展和增强。

本文将继续夯实基础,聊聊它的另外两个核心API:配置管理器ConfigurationManager和动态属性支持DynamicPropertySupport


正文

ConfigurationManager:配置管理器。目的是屏蔽使用者对Configuration这个API的感知,让其只懂如何调用即可。

DynamicPropertySupport:对属性的动态化提供支持的接口。同时也顺便用于解耦动态属性对Commons Configuration的依赖(虽然目前唯一实现只有它)


ConfigurationManager

配置管理器,管理系统范围配置和部署上下文DeploymentContext的中心位置。

它是个工具类,所有方法均以static静态方法提供:

public class ConfigurationManager {

	// 注意:volatile关键字保证了内存可见性
	static volatile AbstractConfiguration instance = null;
	// 标志:是否自定义的Configuration安装进来了
	// true:使用的你自定义的Configuration实例
	// false:使用系统默认的实例(默认)
	static volatile boolean customConfigurationInstalled = false;
	private static volatile ConfigMBean configMBean = null;
	// 部署上下文接口
	static volatile DeploymentContext context = null;

	// initStack将在配置管理器静态初始化时保存堆栈跟踪
	// 帮助调试何时何地创建了ConfigurationManager。我们这样做是为了帮助调试问题
	private static StackTraceElement[] initStack = null;

	... // 省略一些常量
	
	// 静态代码块:完成一些静态初始化动作
    static {
    	initStack = Thread.currentThread().getStackTrace();

		// 1、根据系统属性,创建一个AbstractConfiguration
		String className = System.getProperty("archaius.default.configuration.class");
		String factoryName = System.getProperty("archaius.default.configuration.factory");
		instance = (AbstractConfiguration) Class.forName(className).newInstance();
		customConfigurationInstalled = true;
		...
		... // 同样的逻辑给`DeploymentContext context`赋值
    }
	public static void setDeploymentContext(DeploymentContext context) { ... }

    public static StackTraceElement[] getStaticInitializationSource() {
        return initStack;
    }
    // AbstractConfiguration instance是否已经初始化了
    // 只要有这些系统属性就能初始化,否则是不行的了
    public static synchronized boolean isConfigurationInstalled() {
        return customConfigurationInstalled;
    }
}

静态代码块的初始化过程,可总结为如下步骤:

  1. 记录当前堆栈,方便查问题(毕竟静态代码块你可能不知道是啥时候执行的)
  2. 给静态成员变量AbstractConfiguration instance赋值:
    1. 若有系统属性archaius.default.configuration.class,那就使用Class.newInstance()创建实例(使用自定义的)
    2. 若有系统属性archaius.default.configuration.factory,那就反射调用其getInstance()方法得到实例(使用自定义的)
    3. 若二者均没有,那就交给系统帮你构建
  3. 给静态成员变量DeploymentContext context赋值,逻辑完全同上,两个key是:
    1. archaius.default.deploymentContext.class
    2. archaius.default.deploymentContext.factory
    3. 它顺带会把DeploymentContext.ContextKey.values()所有key放进上的instance里面

作为一个管理器,自然有添加、获取等方法,看看这些主要的API:

ConfigurationManager:

	// 如果静态代码块里没有初始化,这里就交给系统默认帮你完成初始化
	//  该方法是Synchronized同步方法,双重校验锁
    public static AbstractConfiguration getConfigInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
					// getBoolean()没有此key默认返回false
					// 系统属性archaius.dynamicProperty.disableDefaultConfig= true可以关闭默认行为
                    instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
                }
            }
        }
        return instance;
    }
    // 默认行为做了两件事:创建instance实例 以及注册MBean支持JMX
    // 当然:开启JMX支持与否由archaius.dynamicPropertyFactory.registerConfigWithJMX属性指定,默认是false
    private static AbstractConfiguration getConfigInstance(boolean defaultConfigDisabled) {
        if (instance == null && !defaultConfigDisabled) {
            instance = createDefaultConfigInstance();
            registerConfigBean();
        }
        return instance;        
    }

	// 创建默认的Configuration,它是一个ConcurrentCompositeConfiguration组合的配置
	private static AbstractConfiguration createDefaultConfigInstance() {
		ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();  
		...
		new DynamicURLConfiguration()
		new SystemConfiguration()
		new EnvironmentConfiguration()
		...
		return config;
	}

该管理器主要管理着一个AbstractConfiguration,而这个实例的创建方式你可以选择:

  • 自己指定:设置如上两个系统属性均可
  • 自动创建:若没有手动指定,那就走自动创建逻辑。

默认创建的AbstractConfiguration实例是一个组合配置ConcurrentCompositeConfiguration,它包含有如下三种Configuration

  • SystemConfiguration
  • EnvironmentConfiguration
  • DynamicURLConfiguration

前两个好说,最主要是DynamicURLConfiguration这个喽,它会自动去加载类路径下名为config.properties的资源以及你自己指定的archaius.configurationSource.additionalUrls下的资源等,并且开启对它们的自动轮询

说明:关于DynamicURLConfiguration的使用是上篇文章的详细内容,本文不做详细描述,请移步查看

其它的相关方法如:getPropertiesFromFile/loadProperties/loadPropertiesFromConfiguration等等比较简单,就不用过多介绍了。


使用示例

@Test
public void fun1() {
    // 为方便打印,禁用调用系统属性们
    System.setProperty(ConfigurationManager.DISABLE_DEFAULT_SYS_CONFIG, "true");
    System.setProperty(ConfigurationManager.DISABLE_DEFAULT_ENV_CONFIG, "true");
    // config.removeConfiguration(ConfigurationManager.SYS_CONFIG_NAME);
    // config.removeConfiguration(ConfigurationManager.ENV_CONFIG_NAME);
    
    // 自定义Configuration实现
    // System.setProperty("archaius.default.configuration.class", "com.xxxx.XXXConfiuration");

    ConcurrentCompositeConfiguration config = (ConcurrentCompositeConfiguration) ConfigurationManager.getConfigInstance();
    ConfigurationUtils.dump(config, System.out);
    System.out.println("\n=================================");

    Properties properties = new Properties();
    properties.put("age", 18);
    // ConfigurationManager.loadPropertiesFromConfiguration();
    ConfigurationManager.loadProperties(properties);
    ConfigurationUtils.dump(config, System.out);
    System.out.println("\n=================================");
}

运行程序,打印:

name=YourBatman
=================================
name=YourBatman
age=18
=================================

config.properties里的属性是支持动态修改的,是DynamicURLConfiguration提供的支持,这里就不做过多演示了,详情见前一篇文章。

另外,可以看到ConfigurationManager.loadProperties(properties);都是新增,而非覆盖的形式。

说明:底层使用的是config.setProperty(),所以如果是相同key,那就后者覆盖前者喽


DynamicPropertySupport

动态属性支持接口。对于该接口的实现,官方说了:大多数情况下辅以Apache Commons Configuration来实现会容易很多,但这个接口可以让你在不依赖于Apache Commons Configuration库仍可以正常使用。

说明:说是这么说,但是依赖于Apache Commons Configuration实现更方面,为什么不呢?

public interface DynamicPropertySupport {

	// 获取指定属性的值,的字符串表现形式
	// 该值会被缓存,然后被转换为DynamicProperty的指定类型
	String getString(String propName);
	// 添加属性更改监听器。这对于DynamicProperty是必需的
	// 在底层DynamicPropertySupport中更新属性后接收回调
	void addConfigurationListener(PropertyListener expandedPropertyListener);
}

它仅有一个内置实现:ConfigurationBackedDynamicPropertySupportImpl


ConfigurationBackedDynamicPropertySupportImpl

从命名ConfigurationBacked中可以看出它表达的意思:基于org.apache.commons.configuration.Configuration支持的实现。

public class ConfigurationBackedDynamicPropertySupportImpl implements DynamicPropertySupport {

	private final AbstractConfiguration config;
	... // 省略构造器,必须给config赋值
	
	// 如果是String直接返回
	// 若是String数组:转换为逗号分隔的字符串
	// 若是其它类型(如Object类型),直接toString()
    @Override
    public String getString(String key) {
        try {
            String values[] = config.getStringArray(key);
            if (values == null) {
                return null;
            }
            if (values.length == 0) {
                return config.getString(key);
            } else if (values.length == 1) {
                return values[0];
            }

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < values.length; i++) {
                sb.append(values[i]);
                if (i != values.length - 1) {
                    sb.append(",");
                }
            }
            return sb.toString();
        } catch (Exception e) {
            Object v = config.getProperty(key);
            if (v != null) {
                return String.valueOf(v);
            } else {
                return null;
            }
        }
    }


	// 它是实现了org.apache.commons.configuration.event.ConfigurationListener的几个监听器实现类
	// 目的当然是适配`PropertyListener`监听器喽,当属性改变时触发对应事件
    @Override
    public void addConfigurationListener(PropertyListener expandedConfigListener) {
        ExpandedConfigurationListenerAdapter nl = new ExpandedConfigurationListenerAdapter(expandedConfigListener);
        config.addConfigurationListener(nl);    
    }
}

虽然说动态属性DynamicProperty的API设计可以通过DynamicPropertySupport``Apache Commons Configuration,但是实际使用中使用的肯定是ConfigurationBackedDynamicPropertySupportImpl,因为我们不可能自己去书写一套配置管理。


使用示例

@Test
public void fun2() throws ConfigurationException {
    PropertiesConfiguration config = new PropertiesConfiguration("config.properties");

    ConfigurationBackedDynamicPropertySupportImpl dynamicPropertySupport = new ConfigurationBackedDynamicPropertySupportImpl(config);
    // 添加一个PropertyListener监听器,监听属性的增加、set(修改)、清空
    dynamicPropertySupport.addConfigurationListener(new AbstractDynamicPropertyListener() {
        @Override
        public void handlePropertyEvent(String name, Object value, EventType eventType) {
            System.out.printf("事件类型:%s key是 %s 修改后的值是 %s\n", eventType, name, value);
            System.out.println("-------------------------------------");
        }

        // 请注意:这个事件对应AbstractConfiguration.EVENT_CLEAR,若你自己不实现,父类是空实现的哦
        @Override
        public void clear(Object source, boolean beforeUpdate) {
            //EventType里并没有对应它的实现,所以这样是行不通的
            // if (!beforeUpdate) {
            //     handlePropertyEvent(null, null, EventType.CLEAR);
            // }
            if (source instanceof Configuration && beforeUpdate) { // 必须是before,因为after就没值啦
                System.out.println("清空所有的事件,清空的值情况如下:");
                ConfigurationUtils.dump((Configuration) source, System.out);
                System.out.println("\n-------------------------------------");
            }
        }
    });

    config.addProperty("age", 18);
    config.setProperty("age", 20);
    config.clearProperty("name"); // 事件:AbstractConfiguration.EVENT_CLEAR_PROPERTY
    config.clear(); // 事件:AbstractConfiguration.EVENT_CLEAR
}

控制台打印:

事件类型:ADD key是 age 修改后的值是 18
-------------------------------------
事件类型:SET key是 age 修改后的值是 20
-------------------------------------
事件类型:CLEAR key是 name 修改后的值是 null
-------------------------------------
清空所有的事件,清空的值情况如下:
age=20
-------------------------------------

DynamicPropertySupport的主要作用便是这样:当你属性发生变更时,能对应的发出修改事件从而方便做出对应响应。DynamicProperty便是通过其内置的DynamicPropertyListener监听器来实现属性动态化的,下文详解。


总结

关于Netflix Archaiu的两大核心API:ConfigurationManagerDynamicPropertySupport就介绍到这了,有了前面的基础普遍,本文理解起来相对容易。

至此我们已经了解到了Netflix Archaiu动态属性的核心支持,为接下来掌握其动态属性的使用、原理打下了坚持基础,下文将会介绍DynamicProperty
分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!