热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Apollo源码解析——客户端API配置(三)之ConfigFile

1.概述老艿艿:本系列假定胖友已经阅读过《Apollo官方wiki文档》,特别是《Java客户端使用指南》。本文接《Apollo源码解析——客户端API
1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Java 客户端使用指南》 。

本文接 《Apollo 源码解析 —— 客户端 API 配置(二)之一览》 一文,分享 ConfigFile 接口,及其子类,如下图:

ConfigFile 类图

从实现上,ConfigFile 和 Config 超级类似,所以本文会写的比较简洁。

  • Config 基于 KV 数据结构。
  • ConfigFile 基于 String 数据结构。

2. ConfigFile

在 《Apollo 源码解析 —— 客户端 API 配置(一)之一览》 的 「3.2 ConfigFile」 中,有详细分享。

3. AbstractConfigFile

com.ctrip.framework.apollo.internals.AbstractConfigFile ,实现 ConfigFile、RepositoryChangeListener 接口,ConfigFile 抽象类,实现了 1)异步通知监听器、2)计算属性变化等等特性,是 AbstractConfig + DefaultConfig 的功能子集

3.1 构造方法

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 m_listeners = Lists.newCopyOnWriteArrayList();protected ConfigRepository m_configRepository;
/*** 配置 Properties 的缓存引用*/
protected AtomicReference m_configProperties;static {m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory.create("ConfigFile", true));
}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);}
}

3.2 获得内容

交给子类自己实现。

3.3 获得 Namespace 名字

&#64;Override
public String getNamespace() {return m_namespace;
}

3.4 添加配置变更监听器 

&#64;Override
public void addChangeListener(ConfigFileChangeListener listener) {if (!m_listeners.contains(listener)) {m_listeners.add(listener);}
}

3.5 触发配置变更监听器们 

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

3.6 onRepositoryChange

#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 实现类。

4.1 构造方法

private static final Logger logger &#61; LoggerFactory.getLogger(PropertiesConfigFile.class);/*** 配置字符串缓存*/
protected AtomicReference m_contentCache;public PropertiesConfigFile(String namespace, ConfigRepository configRepository) {super(namespace, configRepository);m_contentCache &#61; new AtomicReference<>();
}

  • 因为 Properties 是 KV 数据结构&#xff0c;需要将多条 KV 拼接成一个字符串&#xff0c;进行缓存到 m_contentCache 中。

4.2 更新内容

&#64;Override
protected void update(Properties newProperties) {// 设置【新】Propertiesm_configProperties.set(newProperties);// 清空缓存m_contentCache.set(null);
}

4.3 获得内容 

&#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();
}

  • 从实现代码&#xff0c;我们可以看出&#xff0c;拼接的字符串&#xff0c;每一行一个 KV 属性。例子如下&#xff1a; 

key2&#61;value2
key1&#61;value1

 4.4 获得 Namespace 名字

&#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 。

5.1 XmlConfigFile

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

5.2 JsonConfigFile

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

5.3 YamlConfigFile

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

5.4 YmlConfigFile

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

 


推荐阅读
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
author-avatar
手机用户2602927443
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有