Apollo 源码解析 —— 客户端 API 配置(四)之 ConfigRepository
总阅读量:4612次
摘要: 原创出处 http://www.iocoder.cn/Apollo/client-config-api-4/ 「芋道源码」欢迎转载,保留摘要,谢谢!
阅读源码最好的方式,是使用 IDEA 进行调试 Apollo 源码,不然会一脸懵逼。
胖友可以点击「芋道源码」扫码关注,回复 git018 关键字
获得艿艿添加了中文注释的 Apollo 源码地址。阅读源码很孤单,加入源码交流群,一起坚持!
1. 概述
老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Java 客户端使用指南》 。
本文接 《Apollo 源码解析 —— 客户端 API 配置(二)之一览》 一文,分享 ConfigRepository 接口,及其子类,如下图:
- ConfigRepository、AbstractConfigRepository、RemoteConfigRepository ,在 《Apollo 源码解析 —— Client 轮询配置》 中已经完整解析,所以本文仅分享 LocalConfigRepository 的实现。
在 《Apollo 源码解析 —— 客户端 API 配置(一)之一览》 的 「5.2.1.4 创建 LocalConfigRepository 对象」 中,我们简单定义 ConfigRepository 如下:
这里我们可以简单( 但不完全准确 )理解成配置的 Repository ,负责从远程的 Config Service 读取配置。
- 为什么笔者会说 但不完全准确 呢?答案在 LocalConfigRepository 的实现中。
2. LocalFileConfigRepository
com.ctrip.framework.apollo.internals.LocalFileConfigRepository
,实现 RepositoryChangeListener 接口,继承 AbstractConfigRepository 抽象类,本地文件配置 Repository 实现类。
重点在 「2. 6 sync」 方法。
2.1 构造方法
/** |
m_baseDir
字段,本地缓存配置文件目录( 😈 闭合比较好奇的是,为什么不直接是配置文件,而是配置文件目录,从代码看下来是非目录啊 )。在构造方法中,进行初始化,胖友先跳到 「2.2 findLocalCacheDir」 和 「2.3 setLocalCacheDir」 。m_fileProperties
字段,配置文件 Properties 。m_upstream
字段,上游的 ConfigRepository 对象。一般情况下,使用 RemoteConfigRepository 对象,读取远程 Config Service 的配置。在构造方法中,调用#setUpstreamRepository(ConfigRepository)
方法,设置m_upstream
属性,初始拉取 Config Service 的配置,并监听配置变化。详细解析,胖友先跳到 「2.4 setUpstreamRepository」 。- 调用
#trySync()
方法,同步配置。详细解析,见 「2.6 sync」 。
2.2 findLocalCacheDir
#findLocalCacheDir()
方法,获得本地缓存目录。代码如下:
private File findLocalCacheDir() { |
-
调用
ConfigUtil#getDefaultLocalCacheDir()
方法,获得默认缓存配置目录。代码如下:public String getDefaultLocalCacheDir() {
String cacheRoot = isOSWindows() ? "C:\\opt\\data\\%s" : "/opt/data/%s";
return String.format(cacheRoot, getAppId()); // appId
}- 在非 Windows 的环境下,是
/opt/data/${appId}
目录。
- 在非 Windows 的环境下,是
-
调用
Files#exists(path)
方法,判断若默认缓存配置目录不存在,进行创建。😈但是,可能我们的应用程序没有该目录的权限,此时会导致创建失败。那么就有会出现两种情况:- 第一种,有权限,使用
/opt/data/${appId}/
+config-cache
目录。 - 第二种,无权限,使用 ClassPath/ +
config-cache
目录。这个目录,应用程序下,肯定是有权限的。
- 第一种,有权限,使用
2.3 setLocalCacheDir
调用 #setLocalCacheDir(baseDir, syncImmediately)
方法,设置 m_baseDir
字段。代码如下:
void setLocalCacheDir(File baseDir, boolean syncImmediately) { |
- 调用
#checkLocalConfigCacheDir(baseDir)
方法,校验本地缓存配置目录是否存在。若不存在,则进行创建。详细解析,见 「2.3.1 checkLocalConfigCacheDir」 。 - 若
syncImmediately = true
,则进行同步。目前仅在单元测试中,会出现这种情况。正式的代码,syncImmediately = false
。
2.3.1 checkLocalConfigCacheDir
#checkLocalConfigCacheDir(baseDir)
方法,校验本地缓存配置目录是否存在。若不存在,则进行创建。代码如下:
private void checkLocalConfigCacheDir(File baseDir) { |
- 是不是有点懵逼?该方法校验和创建的
config-cache
目录。这个目录在#findLocalCacheDir()
方法中,并未创建。
2.3.2 assembleLocalCacheFile
那么完整的缓存配置文件到底路径是什么呢?${baseDir}/config-cache/
+ ${appId}+${cluster} + ${namespace}.properties
,即 #assembleLocalCacheFile(baseDir, namespace)
方法,拼接完整的本地缓存配置文件的地址。代码如下:
File assembleLocalCacheFile(File baseDir, String namespace) { |
- 这也是笔者疑惑的,【配置文件】是可以固定下来的。
2.3.3 loadFromLocalCacheFile
#loadFromLocalCacheFile(baseDir, namespace)
方法,从缓存配置文件,读取 Properties 。代码如下:
private Properties loadFromLocalCacheFile(File baseDir, String namespace) { |
2.3.4 persistLocalCacheFile
#loadFromLocalCacheFile(baseDir, namespace)
方法,向缓存配置文件,写入 Properties 。代码如下:
和
#loadFromLocalCacheFile(baseDir, namespace)
方法,相反。
void persistLocalCacheFile(File baseDir, String namespace) { |
2.3.5 updateFileProperties
#updateFileProperties(newProperties)
方法,若 Properties 发生变化,向缓存配置文件,写入 Properties 。代码如下:
老艿艿:在
#persistLocalCacheFile(baseDir, namespace)
方法,进一步封装。
private synchronized void updateFileProperties(Properties newProperties) { |
2.4 setUpstreamRepository
#setUpstreamRepository(ConfigRepository)
方法,设置 m_upstream
属性,初始拉取 Config Service 的配置,并监听配置变化。代码如下:
老艿艿:此处 ConfigRepository 以 RemoteConfigRepository 举例子。实际代码实现里,也是它。
1: |
- 第 6 至 10 行:调用
ConfigRepository#removeChangeListener(RepositoryChangeLister)
方法,从老的m_upstream
中,移除自己( 监听器 )。否则,会错误监听。 - 第 12 行:设置新的
m_upstream
。 - 第 14 行:调用
#trySyncFromUpstream()
方法,从m_upstream
拉取初始配置。详细解析,见 「2.5 trySyncFromUpstream」 。 - 第 16 行:调用
ConfigRepository#addChangeListener(RepositoryChangeLister)
方法,向新的m_upstream
中,注册自己( 监听器 ) 。从而实现 Config Service 配置变更的监听。这也是为什么 LocalFileConfigRepository 实现了 RepositoryChangeListener 接口的原因。整体的监听和通知如下图:
2.5 trySyncFromUpstream
#trySyncFromUpstream()
方法,从 m_upstream
拉取初始配置,并返回是否拉取成功。代码如下:
1: private boolean trySyncFromUpstream() { |
- 第 2 至 4 行:当
m_upstream
为空时,返回拉取失败false
。 - 第 7 行:调用
ConfigRepository#getConfig()
方法,从m_upstream
拉取配置 Properties 。 - 第 9 行:调用
#updateFileProperties(properties)
方法,更新到m_fileProperties
中。 - 第 11 行:返回同步成功
true
。 - 第 17 行:返回同步失败
false
。
那么,为什么要返回同步结果呢?答案在 「2.6 sync」 中。
2.6 sync
埋了这么多的伏笔( 代码 ),我们将要本文最重要的方法 #sync()
!!!
在非本地模式的情况下,LocalFileConfigRepository 在初始化时,会首先从远程 Config Service 同步( 加载 )配置。若同步(加载)失败,则读取本地缓存的配置文件。
在本地模式的情况下,则只读取本地缓存的配置文件。当然,严格来说,也不一定是缓存,可以是开发者,手动创建的配置文件。
实现代码如下:
|
- 结合代码注释 + 上述说明,理解下具体的代码。
2.7 onRepositoryChange
当 RemoteRepositoryConfig 读取到配置变更时,调用 #onRepositoryChange(name, newProperties)
方法,更新 m_fileProperties
,并通知监听器们。代码如下:
|
2.8 getConfig
|
666. 彩蛋
哇哈哈,开始 Apollo 客户端的 Spring 的整合方式。