老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Java 客户端使用指南》 。
本文接 《Apollo 源码解析 —— 客户端 API 配置(二)之一览》 一文,分享 ConfigFile 接口,及其子类,如下图:
从实现上,ConfigFile 和 Config 超级类似,所以本文会写的比较简洁。
在 《Apollo 源码解析 —— 客户端 API 配置(一)之一览》 的 「3.2 ConfigFile」 中,有详细分享。
3. AbstractConfigFilecom.ctrip.framework.apollo.internals.AbstractConfigFile
,实现 ConfigFile、RepositoryChangeListener 接口,ConfigFile 抽象类,实现了 1)异步通知监听器、2)计算属性变化等等特性,是 AbstractConfig + DefaultConfig 的功能子集。
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigFile.class);/*** ExecutorService 对象,用于配置变化时,异步通知 ConfigChangeListener 监听器们** 静态属性,所有 Config 共享该线程池。*/
private static ExecutorService m_executorService;/*** Namespace 的名字*/
protected String m_namespace;
/*** ConfigChangeListener 集合*/
private List
/*** 配置 Properties 的缓存引用*/
protected AtomicReference
}public AbstractConfigFile(String namespace, ConfigRepository configRepository) {m_configRepository &#61; configRepository;m_namespace &#61; namespace;m_configProperties &#61; new AtomicReference<>();// 初始化initialize();
}private void initialize() {try {// 初始化 m_configPropertiesm_configProperties.set(m_configRepository.getConfig());} catch (Throwable ex) {Tracer.logError(ex);logger.warn("Init Apollo Config File failed - namespace: {}, reason: {}.", m_namespace, ExceptionUtil.getDetailMessage(ex));} finally {//register the change listener no matter config repository is working or not//so that whenever config repository is recovered, config could get changed// 注册到 ConfigRepository 中&#xff0c;从而实现每次配置发生变更时&#xff0c;更新配置缓存 &#96;m_configProperties&#96; 。m_configRepository.addChangeListener(this);}
}
交给子类自己实现。
&#64;Override
public String getNamespace() {return m_namespace;
}
&#64;Override
public void addChangeListener(ConfigFileChangeListener listener) {if (!m_listeners.contains(listener)) {m_listeners.add(listener);}
}
private void fireConfigChange(final ConfigFileChangeEvent changeEvent) {// 缓存 ConfigChangeListener 数组for (final ConfigFileChangeListener listener : m_listeners) {m_executorService.submit(new Runnable() {&#64;Overridepublic void run() {String listenerName &#61; listener.getClass().getName();Transaction transaction &#61; Tracer.newTransaction("Apollo.ConfigFileChangeListener", listenerName);try {// 通知监听器listener.onChange(changeEvent);transaction.setStatus(Transaction.SUCCESS);} catch (Throwable ex) {transaction.setStatus(ex);Tracer.logError(ex);logger.error("Failed to invoke config file change listener {}", listenerName, ex);} finally {transaction.complete();}}});}
}
#onRepositoryChange(namespace, newProperties)
方法&#xff0c;当 ConfigRepository 读取到配置发生变更时&#xff0c;计算配置变更集合&#xff0c;并通知监听器们。代码如下&#xff1a;
&#64;Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {// 忽略&#xff0c;若未变更if (newProperties.equals(m_configProperties.get())) {return;}// 读取新的 Properties 对象Properties newConfigProperties &#61; new Properties();newConfigProperties.putAll(newProperties);// 获得【旧】值String oldValue &#61; getContent();// 更新为【新】值update(newProperties);// 获得新值String newValue &#61; getContent();// 计算变化类型PropertyChangeType changeType &#61; PropertyChangeType.MODIFIED;if (oldValue &#61;&#61; null) {changeType &#61; PropertyChangeType.ADDED;} else if (newValue &#61;&#61; null) {changeType &#61; PropertyChangeType.DELETED;}// 通知监听器们this.fireConfigChange(new ConfigFileChangeEvent(m_namespace, oldValue, newValue, changeType));Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
}
#update(newProperties)
抽象方法&#xff0c;更新为【新】值。该方法需要子类自己去实现。抽象方法如下&#xff1a; protected abstract void update(Properties newProperties);
4. PropertiesConfigFile
com.ctrip.framework.apollo.internals.PropertiesConfigFile
&#xff0c;实现 AbstractConfigFile 抽象类&#xff0c;类型为 .properties
的 ConfigFile 实现类。
private static final Logger logger &#61; LoggerFactory.getLogger(PropertiesConfigFile.class);/*** 配置字符串缓存*/
protected AtomicReference
}
m_contentCache
中。&#64;Override
protected void update(Properties newProperties) {// 设置【新】Propertiesm_configProperties.set(newProperties);// 清空缓存m_contentCache.set(null);
}
&#64;Override
public String getContent() {// 更新到缓存if (m_contentCache.get() &#61;&#61; null) {m_contentCache.set(doGetContent());}// 从缓存中&#xff0c;获得配置字符串return m_contentCache.get();
}String doGetContent() {if (!this.hasContent()) {return null;}try {return PropertiesUtil.toString(m_configProperties.get()); // 拼接 KV 属性&#xff0c;成字符串} catch (Throwable ex) {ApolloConfigException exception &#61; new ApolloConfigException(String.format("Parse properties file content failed for namespace: %s, cause: %s", m_namespace, ExceptionUtil.getDetailMessage(ex)));Tracer.logError(exception);throw exception;}
}&#64;Override
public boolean hasContent() {return m_configProperties.get() !&#61; null && !m_configProperties.get().isEmpty();
}
PropertiesUtil#toString(Properties)
方法&#xff0c;将 Properties 拼接成字符串。代码如下&#xff1a; /*** Transform the properties to string format** &#64;param properties the properties object* &#64;return the string containing the properties* &#64;throws IOException*/
public static String toString(Properties properties) throws IOException {StringWriter writer &#61; new StringWriter();properties.store(writer, null);StringBuffer stringBuffer &#61; writer.getBuffer();// 去除头部自动添加的注释filterPropertiesComment(stringBuffer);return stringBuffer.toString();
}/*** filter out the first comment line** &#64;param stringBuffer the string buffer* &#64;return true if filtered successfully, false otherwise*/
static boolean filterPropertiesComment(StringBuffer stringBuffer) {//check whether has comment in the first lineif (stringBuffer.charAt(0) !&#61; &#39;#&#39;) {return false;}int commentLineIndex &#61; stringBuffer.indexOf("\n");if (commentLineIndex &#61;&#61; -1) {return false;}stringBuffer.delete(0, commentLineIndex &#43; 1);return true;
}
Properties#store(writer, null)
方法&#xff0c;会自动在首行&#xff0c;添加注释时间。代码如下&#xff1a; private void store0(BufferedWriter bw, String comments, boolean escUnicode)throws IOException
{if (comments !&#61; null) {writeComments(bw, comments);}bw.write("#" &#43; new Date().toString()); // 自动在**首行**&#xff0c;添加**注释时间**。bw.newLine();synchronized (this) {for (Enumeration> e &#61; keys(); e.hasMoreElements();) {String key &#61; (String)e.nextElement();String val &#61; (String)get(key);key &#61; saveConvert(key, true, escUnicode);/* No need to escape embedded and trailing spaces for value, hence* pass false to flag.*/val &#61; saveConvert(val, false, escUnicode);bw.write(key &#43; "&#61;" &#43; val);bw.newLine();}}bw.flush();
}
key2&#61;value2
key1&#61;value1
&#64;Override
public ConfigFileFormat getConfigFileFormat() {return ConfigFileFormat.Properties;
}
5. PlainTextConfigFile
com.ctrip.framework.apollo.internals.PlainTextConfigFile
&#xff0c;实现 AbstractConfigFile 抽象类&#xff0c;纯文本 ConfigFile 抽象类&#xff0c;例如 xml
yaml
等等。
更新内容
&#64;Override
protected void update(Properties newProperties) {m_configProperties.set(newProperties);
}
获得内容
&#64;Override
public String getContent() {if (!this.hasContent()) {return null;}return m_configProperties.get().getProperty(ConfigConsts.CONFIG_FILE_CONTENT_KEY);
}&#64;Override
public boolean hasContent() {if (m_configProperties.get() &#61;&#61; null) {return false;}return m_configProperties.get().containsKey(ConfigConsts.CONFIG_FILE_CONTENT_KEY);
}
"content"
配置项&#xff0c;获得配置文本。这也是为什么类名以 PlainText 开头的原因。&#x1f642; PlainTextConfigFile 的子类&#xff0c;代码基本一致&#xff0c;差别在于 #getConfigFileFormat()
实现方法&#xff0c;返回不同的 ConfigFileFormat 。
com.ctrip.framework.apollo.internals.XmlConfigFile
&#xff0c;实现 PlainTextConfigFile 抽象类&#xff0c;类型为 .xml
的 ConfigFile 实现类。代码如下&#xff1a;
public class XmlConfigFile extends PlainTextConfigFile {public XmlConfigFile(String namespace, ConfigRepository configRepository) {super(namespace, configRepository);}&#64;Overridepublic ConfigFileFormat getConfigFileFormat() {return ConfigFileFormat.XML;}}
com.ctrip.framework.apollo.internals.JsonConfigFile
&#xff0c;实现 PlainTextConfigFile 抽象类&#xff0c;类型为 .json
的 ConfigFile 实现类。代码如下&#xff1a;
public class JsonConfigFile extends PlainTextConfigFile {public JsonConfigFile(String namespace,ConfigRepository configRepository) {super(namespace, configRepository);}&#64;Overridepublic ConfigFileFormat getConfigFileFormat() {return ConfigFileFormat.JSON;}}
com.ctrip.framework.apollo.internals.YamlConfigFile
&#xff0c;实现 PlainTextConfigFile 抽象类&#xff0c;类型为 .yaml
的 ConfigFile 实现类。代码如下&#xff1a;
public class YamlConfigFile extends PlainTextConfigFile {public YamlConfigFile(String namespace, ConfigRepository configRepository) {super(namespace, configRepository);}&#64;Overridepublic ConfigFileFormat getConfigFileFormat() {return ConfigFileFormat.YAML;}}
com.ctrip.framework.apollo.internals.YmlConfigFile
&#xff0c;实现 PlainTextConfigFile 抽象类&#xff0c;类型为 .yaml
的 ConfigFile 实现类。代码如下&#xff1a;
public class YmlConfigFile extends PlainTextConfigFile {public YmlConfigFile(String namespace, ConfigRepository configRepository) {super(namespace, configRepository);}&#64;Overridepublic ConfigFileFormat getConfigFileFormat() {return ConfigFileFormat.YML;}}