本文接 《Apollo 源码解析 —— 客户端 API 配置(二)之一览》 一文,分享 ConfigFile 接口,及其子类,如下图:
从实现上,ConfigFile 和 Config 超级类似,所以本文会写的比较简洁。
Config 基于 KV 数据结构。 ConfigFile 基于 String 数据结构。2. ConfigFile在 《Apollo 源码解析 —— 客户端 API 配置(一)之一览》 的 「3.2 ConfigFile」 中,有详细分享。
3. AbstractConfigFilecom.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 ConfigFileChangeListener m_listeners = Lists.newCopyOnWriteArrayList();protected ConfigRepository m_configRepository; * 配置 Properties 的缓存引用protected AtomicReference Properties m_configProperties;static { m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory.create(\"ConfigFile\", true));public AbstractConfigFile(String namespace, ConfigRepository configRepository) { m_configRepository = configRepository; m_namespace = namespace; m_configProperties = new AtomicReference (); // 初始化 initialize();private void initialize() { try { // 初始化 m_configProperties m_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 中,从而实现每次配置发生变更时,更新配置缓存 `m_configProperties` 。 m_configRepository.addChangeListener(this);}3.2 获得内容
交给子类自己实现。
3.3 获得 Namespace 名字@Overridepublic String getNamespace() { return m_namespace;}3.4 添加配置变更监听器
@Overridepublic 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() { @Override public void run() { String listenerName = listener.getClass().getName(); Transaction transaction = 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) 方法,当 ConfigRepository 读取到配置发生变更时,计算配置变更集合,并通知监听器们。代码如下:
@Overridepublic synchronized void onRepositoryChange(String namespace, Properties newProperties) { // 忽略,若未变更 if (newProperties.equals(m_configProperties.get())) { return; // 读取新的 Properties 对象 Properties newConfigProperties = new Properties(); newConfigProperties.putAll(newProperties); // 获得【旧】值 String oldValue = getContent(); // 更新为【新】值 update(newProperties); // 获得新值 String newValue = getContent(); // 计算变化类型 PropertyChangeType changeType = PropertyChangeType.MODIFIED; if (oldValue == null) { changeType = PropertyChangeType.ADDED; } else if (newValue == null) { changeType = PropertyChangeType.DELETED; // 通知监听器们 this.fireConfigChange(new ConfigFileChangeEvent(m_namespace, oldValue, newValue, changeType)); Tracer.logEvent(\"Apollo.Client.ConfigChanges\", m_namespace);}调用 #update(newProperties) 抽象方法,更新为【新】值。该方法需要子类自己去实现。抽象方法如下:
protected abstract void update(Properties newProperties);4. PropertiesConfigFile
com.ctrip.framework.apollo.internals.PropertiesConfigFile ,实现 AbstractConfigFile 抽象类,类型为 .properties 的 ConfigFile 实现类。
4.1 构造方法private static final Logger logger = LoggerFactory.getLogger(PropertiesConfigFile.class); * 配置字符串缓存protected AtomicReference String m_contentCache;public PropertiesConfigFile(String namespace, ConfigRepository configRepository) { super(namespace, configRepository); m_contentCache = new AtomicReference ();}因为 Properties 是 KV 数据结构,需要将多条 KV 拼接成一个字符串,进行缓存到 m_contentCache 中。4.2 更新内容
@Overrideprotected void update(Properties newProperties) { // 设置【新】Properties m_configProperties.set(newProperties); // 清空缓存 m_contentCache.set(null);}4.3 获得内容
@Overridepublic String getContent() { // 更新到缓存 if (m_contentCache.get() == null) { m_contentCache.set(doGetContent()); // 从缓存中,获得配置字符串 return m_contentCache.get();String doGetContent() { if (!this.hasContent()) { return null; try { return PropertiesUtil.toString(m_configProperties.get()); // 拼接 KV 属性,成字符串 } catch (Throwable ex) { ApolloConfigException exception = new ApolloConfigException(String.format(\"Parse properties file content failed for namespace: %s, cause: %s\", m_namespace, ExceptionUtil.getDetailMessage(ex))); Tracer.logError(exception); throw exception;@Overridepublic boolean hasContent() { return m_configProperties.get() != null !m_configProperties.get().isEmpty();}调用 PropertiesUtil#toString(Properties) 方法,将 Properties 拼接成字符串。代码如下:
* Transform the properties to string format * @param properties the properties object * @return the string containing the properties * @throws IOExceptionpublic static String toString(Properties properties) throws IOException { StringWriter writer = new StringWriter(); properties.store(writer, null); StringBuffer stringBuffer = writer.getBuffer(); // 去除头部自动添加的注释 filterPropertiesComment(stringBuffer); return stringBuffer.toString(); * filter out the first comment line * @param stringBuffer the string buffer * @return true if filtered successfully, false otherwisestatic boolean filterPropertiesComment(StringBuffer stringBuffer) { //check whether has comment in the first line if (stringBuffer.charAt(0) != \'#\') { return false; int commentLineIndex = stringBuffer.indexOf(\"\\n\"); if (commentLineIndex == -1) { return false; stringBuffer.delete(0, commentLineIndex + 1); return true;}因为 Properties#store(writer, null) 方法,会自动在首行,添加注释时间。代码如下:
private void store0(BufferedWriter bw, String comments, boolean escUnicode) throws IOException if (comments != null) { writeComments(bw, comments); bw.write(\"#\" + new Date().toString()); // 自动在**首行**,添加**注释时间**。 bw.newLine(); synchronized (this) { for (Enumeration ? e = keys(); e.hasMoreElements();) { String key = (String)e.nextElement(); String val = (String)get(key); key = saveConvert(key, true, escUnicode); /* No need to escape embedded and trailing spaces for value, hence * pass false to flag. val = saveConvert(val, false, escUnicode); bw.write(key + \"=\" + val); bw.newLine(); bw.flush();}从实现代码,我们可以看出,拼接的字符串,每一行一个 KV 属性。例子如下:
key2=value2key1=value14.4 获得 Namespace 名字
@Overridepublic ConfigFileFormat getConfigFileFormat() { return ConfigFileFormat.Properties;}5. PlainTextConfigFile
com.ctrip.framework.apollo.internals.PlainTextConfigFile ,实现 AbstractConfigFile 抽象类,纯文本 ConfigFile 抽象类,例如 xml yaml 等等。
更新内容
@Overrideprotected void update(Properties newProperties) { m_configProperties.set(newProperties);}
获得内容
@Overridepublic String getContent() { if (!this.hasContent()) { return null; return m_configProperties.get().getProperty(ConfigConsts.CONFIG_FILE_CONTENT_KEY);@Overridepublic boolean hasContent() { if (m_configProperties.get() == null) { return false; return m_configProperties.get().containsKey(ConfigConsts.CONFIG_FILE_CONTENT_KEY);}直接从 \"content\" 配置项,获得配置文本。这也是为什么类名以 PlainText 开头的原因。
本文链接: http://apolloapi.immuno-online.com/view-733045.html