[享学Netflix] 十二、Archaius动态属性DynamicProperty原理详解(重要)

  • 2020 年 3 月 18 日
  • 筆記

要相信:使用这些类库,你的代码将变得更好。 代码下载地址:https://github.com/f641385712/netflix-learning

目录

前言

上篇文章了解到了Netflix Archaius它提供的两个支持类:配置管理器ConfigurationManager和动态属性支持DynamicPropertySupport。特别是DynamicPropertySupport它提供了对动态属性的支持,原理便是通过PropertyListener来完成。

本文将深入了解Netflix Archaius动态属性相关内容,它用DynamicProperty表示一个动态k-v属性值,再结合DynamicPropertySupport提供的监听支持,最终实现属性动态化。


正文

DynamicProperty:动态属性,一个实例对象代表一个k-v,然后具有动态的能力 DynamicPropertyFactory:用于构建一个动态属性实例,屏蔽掉DynamicProperty具体实现,对使用者友好。


DynamicProperty

一个带有缓存的动态配置属性,它具有动态性:当属性变更时会自动更新其缓存。该对象是线程安全的,并且访问速度非常快,高于System.getProperty()

说明:效率高于System得益于ConcurrentHashMap的效率高于Properties(Hashtable)

使用场景:该类用于多次获取属性值,并且该值可能会动态更改的情况。如果属性只读一次,则应使用“普通”访问方法。如果属性值是固定的,请考虑将该值缓存在变量中(用变量表示)。

public class DynamicProperty {  	private volatile static DynamicPropertySupport dynamicPropertySupportImpl;    	// 这就是它速度快的原因。注意是static的哟,需要放置内存泄漏  	private static final ConcurrentHashMap<String, DynamicProperty> ALL_PROPS = new ConcurrentHashMap<>();    	...      private String propName; // 属性名      private String stringValue = null; // 属性值      private long changedTime;      private CopyOnWriteArraySet<Runnable> callbacks = new CopyOnWriteArraySet<>();      ...    	// 重要抽象:特定类型的缓存Value  	// 它的特点是速度快,且支持变化后立马自更新  	private abstract class CachedValue<T> {    		// 这三个属性均是volatile的          private volatile boolean isCached;          private volatile IllegalArgumentException exception;          private volatile T value;    		// 获取值方法:对外public  		public T getValue() throws IllegalArgumentException {  			// 若还没缓存过,那就加锁去缓存  			if (!isCached) {  				synchronized (lock) {  					...  					//缓存的时候有个parse(stringValue);数据转换动作  					// 当然也有可能抛出异常  					parse(stringValue);  					...  				}  			} else {  				... // 返回缓存的值:成员属性value值  			}    		}  		public T getValue(T defaultValue) { ... }  		// 数据转换的抽象方法,子类自行实现  		protected abstract T parse(String rep) throws Exception;  	}    	// 请记住这两个数组:很多简写都表示true哦,这基本是业界规范      private static final String[] TRUE_VALUES =  { "true",  "t", "yes", "y", "on"  };      private static final String[] FALSE_VALUES = { "false", "f", "no",  "n", "off" };      private CachedValue<Boolean> booleanValue = rep -> ...;      private CachedValue<Long> longValue = rep -> Long.valueOf(rep);      private CachedValue<String> cachedStringValue = ...      ...      private CachedValue<Class> classValue= rep -> Class.forName(rep);        // 有了这种缓存值,下面获取就方便了      public String getString() {          return cachedStringValue.getValue();      }      ...      public Class getNamedClass() throws IllegalArgumentException {          return classValue.getValue();      }      public <T> Optional<T> getCachedValue(Class<T> objectType) { ... }      	// 单例设计:获取一个DynamicProperty 实例,才能拿到具体的value值嘛  	// 说明:这个过程的线程安全性是由`ConcurrentHashMap`去保证的      public static DynamicProperty getInstance(String propName) {  		// 空调用:确保配置源Configuration已经注册上了DynamicPropertyListener监听          if (dynamicPropertySupportImpl == null) {              DynamicPropertyFactory.getInstance();          }    		// 先从缓存拿,拿不到就临死构造一个放进缓存里          DynamicProperty prop = ALL_PROPS.get(propName);          if (prop == null) {              prop = new DynamicProperty(propName);              DynamicProperty oldProp = ALL_PROPS.putIfAbsent(propName, prop);              if (oldProp != null) {                  prop = oldProp;              }          }          return prop;      }          private DynamicProperty(String propName) {          this.propName = propName;          updateValue();      }  }

能被缓存的值只有如上类型,当然String类型可以代表任何值,所以它还是通用的。但你是否发现,到目前而止你还不知道如何初始化一个DynamicProperty,也就是给其属性如:propName/stringValue等等赋值,下面就介绍下它的初始化相关相关方法:

DynamicProperty:    	// 唯一初始化方法:初始化一个DynamicProperty并且给其配置好DynamicPropertyListener监听器  	// 从而具有缓存+动态的功能。初始化的时候会updateAllProperties()  	// 特点:此方法非public      static synchronized void initialize(DynamicPropertySupport config) {          dynamicPropertySupportImpl = config;          config.addConfigurationListener(new DynamicPropertyListener());          updateAllProperties();      }      // 该方法含义同上      static void registerWithDynamicPropertySupport(DynamicPropertySupport config) {          initialize(config);      }          // 返回值true:代表值有变化(只要有一个有变换就返回true)      private static boolean updateAllProperties() {          boolean changed = false;          for (DynamicProperty prop : ALL_PROPS.values()) {    			// 更新值来自于:dynamicPropertySupportImpl.getString(propName);  			// 其实也就是Configuration文件里喽              if (prop.updateValue()) {                  prop.notifyCallbacks(); // 若有变化,就触发绑定在该值上的所有回调                  changed = true;              }          }          return changed;      }

这样子就完成了整个属性值的初始化:把Configuration里面所有的实现都初始化为DynamicProperty然后缓存在全局static变量的Map里面,这样子访问速度变快了并且还天然具有动态的能力了。

但是,initialize方法它并非public的,它唯一被(间接)调用处是在DynamicPropertyFactory#initWithConfigurationSource()这个public方法里,这个类文下也会有详解。


DynamicPropertyListener

它是DynamicProperty的一个静态内部类,用于监听 DynamicProperty持有的DynamicPropertySupport,从而可以完成熟悉发生改变时(新增、修改、clear等),执行对应的操作。

DynamicProperty:    	// 初始化DynamicProperty时候完成监听器的注册      static synchronized void initialize(DynamicPropertySupport config) {          dynamicPropertySupportImpl = config;          config.addConfigurationListener(new DynamicPropertyListener());          updateAllProperties();      }
static class DynamicPropertyListener implements PropertyListener {    		// source刚被加载,就可以update所有          @Override          public void configSourceLoaded(Object source) {              updateAllProperties();          }    		// 添加属性时,做相应处理          @Override          public void addProperty(Object source, String name, Object value, boolean beforeUpdate) {          	// 成功后              if (!beforeUpdate) {                  updateProperty(name, value);              } else { // 成功前,先校验                  validate(name, value);              }          }          // 修改一个值时,逻辑完全同上          public void setProperty( ... ){ ... }          // 成功后,updateProperty(name, value);即可          public void clearProperty( ... ){ ... }    		// 清空所有属性时候触发的动作(成功后)          @Override          public void clear(Object source, boolean beforeUpdate) {              if (!beforeUpdate) {                  updateAllProperties();              }          }  }

DynamicProperty的动态性是通过此监听器器来实现的:当org.apache.commons.configuration.Configuration属性发生变化时,该监听器对应方法就会被触发。


使用示例

DynamicProperty并不能直接构建,因此它的使用示例请参见DynamicPropertyFactory


DynamicPropertyFactory

顾名思义,该类表示DynamicProperty动态属性的工厂。创建动态属性实例并将其与底层配置或动态属性支持关联的工厂,在运行时可以动态更改这些属性

该工厂以单例形式使用,单例的初始化逻辑还蛮有看头的:

public class DynamicPropertyFactory {    	private static DynamicPropertyFactory instance = new DynamicPropertyFactory();      private DynamicPropertyFactory() {}    	// 依赖于下面方法,这是内部帮你把AbstractConfiguration适配为了DynamicPropertySupport  	public static DynamicPropertyFactory initWithConfigurationSource(AbstractConfiguration config) { ... }  	// 这个方法也是返回一个工厂的实例:但它要求你必须传入你的DynamicPropertySupport实现类喽(而不能用系统默认的)      public static DynamicPropertyFactory initWithConfigurationSource(DynamicPropertySupport dynamicPropertySupport) {          synchronized (ConfigurationManager.class) {              if (dynamicPropertySupport == null) { // 必传                  throw new IllegalArgumentException("dynamicPropertySupport is null");              }    			// 很明显:这里大概率是ConfigurationBackedDynamicPropertySupportImpl              AbstractConfiguration configuration = null;              if (dynamicPropertySupport instanceof AbstractConfiguration) {                  configuration = (AbstractConfiguration) dynamicPropertySupport;              } else if (dynamicPropertySupport instanceof ConfigurationBackedDynamicPropertySupportImpl) {                  configuration = ((ConfigurationBackedDynamicPropertySupportImpl) dynamicPropertySupport).getConfiguration();              }  			... // 忽略部分校验    			// 这个setDirect有意思了:就是给ConfigurationManager管理的直接设置一些值  			// 值来源于:AbstractConfiguration,比如instance、监听器等等              if (configuration != null && configuration != ConfigurationManager.instance) {                  ConfigurationManager.setDirect(configuration);              }              // 给本类本厂的本类的属性赋值              setDirect(dynamicPropertySupport);              return instance;          }      }      static void setDirect(DynamicPropertySupport support) {          synchronized (ConfigurationManager.class) {              config = support;              // 这就是对DynamicProperty进行init初始化动作喽              // configuration里面所有的属性都会变为动态的,然后缓存着              DynamicProperty.registerWithDynamicPropertySupport(support);              initializedWithDefaultConfig = false;          }      }        	// 一般我们会使用它:因为它默认帮我们构建new ConfigurationBackedDynamicPropertySupportImpl(config)作为实现  	// 不用操心。而使用者只需关心Configuration配置源即可  	// 也就是ConfigurationManager.getConfigInstance()这个逻辑      public static DynamicPropertyFactory getInstance() {          ... // 逻辑同上的initWithConfigurationSource          return instance;      }  }

该工厂能够屏蔽DynamicProperty的初始化以及使用逻辑,让调用者只需关心配置源Configuration即可。当然,它还有一些其它帮助方法:

DynamicPropertyFactory:    	// 这个值:只有Configuration是根据你配置的系统属性来初始化的才是true  	private volatile static boolean initializedWithDefaultConfig = false;    	// 默认情况下:是不会开启JMX支持的哦  	public static final String ENABLE_JMX = "archaius.dynamicPropertyFactory.registerConfigWithJMX";    	// 获取支持的配置源  	// 你可以基于Apache Commons Configuration实现,但不是必须的。所以这里返回的是Object      public static Object getBackingConfigurationSource() {          if (config instanceof ConfigurationBackedDynamicPropertySupportImpl) {              return ((ConfigurationBackedDynamicPropertySupportImpl) config).getConfiguration();          } else {              return config;          }      }    	// 返回一个DynamicStringProperty,内部原理实际为DynamicProperty      public DynamicStringProperty getStringProperty(String propName, String defaultValue) {          return getStringProperty(propName, defaultValue, null);      }      public DynamicStringProperty getStringProperty(String propName, String defaultValue, final Runnable propertyChangeCallback) {          checkAndWarn(propName);          DynamicStringProperty property = new DynamicStringProperty(propName, defaultValue);          addCallback(propertyChangeCallback, property);          return property;      }      ... // 省略getInt、getLong、getDouble等等        // 获取一个带上下文的动态属性      public <T> DynamicContextualProperty<T> getContextualProperty(String propName, T defaultValue) { ... }

DynamicPropertyFactory的设计可以看到,它是面向使用者的API,希望屏蔽掉DynamicProperty的实现细节,使得对调用者更加友好。


使用示例

@Test  public void fun2() throws InterruptedException {      DynamicPropertyFactory propertyFactory = DynamicPropertyFactory.getInstance();      DynamicStringProperty nameProperty = propertyFactory.getStringProperty("name", "defaultName");      nameProperty.addCallback(() -> System.out.println("name属性值发生变化:"));        // 10秒钟读一次      while (true) {          System.out.println(nameProperty.get());          TimeUnit.SECONDS.sleep(50);      }  }

运行程序控制台输出:

YourBatman  name属性值发生变化:  YourBatman-changed

这就是动态属性的特点:你只需要更改你配置文件的值即可。

注意:DynamicProperty默认是1分钟重载一次哦,具体可参照DynamicURLConfiguration这个类

和System性能比较: 其实就是HashtableConcurrentHashMap + cache的性能比较,很显然后者占优,有兴趣者可自行尝试。