Commit 6bed7778e843c56c5163870797d610afcfabd200

Authored by YunaiV
1 parent ec9ed896

提交下,准备简化。

Showing 18 changed files with 726 additions and 24 deletions
src/main/java/cn/iocoder/dashboard/framework/apollox/Config.java
@@ -25,4 +25,11 @@ public interface Config { @@ -25,4 +25,11 @@ public interface Config {
25 */ 25 */
26 Set<String> getPropertyNames(); 26 Set<String> getPropertyNames();
27 27
  28 + /**
  29 + * Add change listener to this config instance.
  30 + *
  31 + * @param listener the config change listener
  32 + */
  33 + void addChangeListener(ConfigChangeListener listener);
  34 +
28 } 35 }
src/main/java/cn/iocoder/dashboard/framework/apollox/ConfigService.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox;
  2 +
  3 +import cn.hutool.core.lang.Singleton;
  4 +
  5 +public class ConfigService {
  6 +
  7 + public static Config getConfig(String namespace) {
  8 + return Singleton.get(DefaultConfig.class);
  9 + }
  10 +
  11 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/DBConfig.java renamed to src/main/java/cn/iocoder/dashboard/framework/apollox/DefaultConfig.java
1 package cn.iocoder.dashboard.framework.apollox; 1 package cn.iocoder.dashboard.framework.apollox;
2 2
  3 +import java.util.Collections;
3 import java.util.Set; 4 import java.util.Set;
4 5
5 -public class DBConfig implements Config { 6 +public class DefaultConfig implements Config {
6 7
7 @Override 8 @Override
8 public String getProperty(String key, String defaultValue) { 9 public String getProperty(String key, String defaultValue) {
@@ -11,7 +12,12 @@ public class DBConfig implements Config { @@ -11,7 +12,12 @@ public class DBConfig implements Config {
11 12
12 @Override 13 @Override
13 public Set<String> getPropertyNames() { 14 public Set<String> getPropertyNames() {
14 - return null; 15 + return Collections.emptySet(); // TODO 等下实现
  16 + }
  17 +
  18 + @Override
  19 + public void addChangeListener(ConfigChangeListener listener) {
  20 +
15 } 21 }
16 22
17 } 23 }
src/main/java/cn/iocoder/dashboard/framework/apollox/internals/AbstractConfigRepository.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.internals;
  2 +
  3 +import org.slf4j.Logger;
  4 +import org.slf4j.LoggerFactory;
  5 +
  6 +import java.util.List;
  7 +import java.util.Properties;
  8 +import java.util.concurrent.CopyOnWriteArrayList;
  9 +
  10 +/**
  11 + * 配置 Repository 抽象类
  12 + *
  13 + * @author Jason Song(song_s@ctrip.com)
  14 + */
  15 +public abstract class AbstractConfigRepository implements ConfigRepository {
  16 +
  17 + private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class);
  18 +
  19 + /**
  20 + * RepositoryChangeListener 数组
  21 + */
  22 + private List<RepositoryChangeListener> m_listeners = new CopyOnWriteArrayList<>();
  23 +
  24 + /**
  25 + * 尝试同步
  26 + *
  27 + * @return 是否同步成功
  28 + */
  29 + protected boolean trySync() {
  30 + try {
  31 + // 同步
  32 + sync();
  33 + // 返回同步成功
  34 + return true;
  35 + } catch (Throwable ex) {
  36 +// Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
  37 + logger.warn("Sync config failed, will retry. Repository {}", getClass(), ex);
  38 + }
  39 + // 返回同步失败
  40 + return false;
  41 + }
  42 +
  43 + /**
  44 + * 同步配置
  45 + */
  46 + protected abstract void sync();
  47 +
  48 + @Override
  49 + public void addChangeListener(RepositoryChangeListener listener) {
  50 + if (!m_listeners.contains(listener)) {
  51 + m_listeners.add(listener);
  52 + }
  53 + }
  54 +
  55 + @Override
  56 + public void removeChangeListener(RepositoryChangeListener listener) {
  57 + m_listeners.remove(listener);
  58 + }
  59 +
  60 + /**
  61 + * 触发监听器们
  62 + *
  63 + * @param namespace Namespace 名字
  64 + * @param newProperties 配置
  65 + */
  66 + protected void fireRepositoryChange(String namespace, Properties newProperties) {
  67 + // 循环 RepositoryChangeListener 数组
  68 + for (RepositoryChangeListener listener : m_listeners) {
  69 + try {
  70 + // 触发监听器
  71 + listener.onRepositoryChange(namespace, newProperties);
  72 + } catch (Throwable ex) {
  73 +// Tracer.logError(ex);
  74 + logger.error("Failed to invoke repository change listener {}", listener.getClass(), ex);
  75 + }
  76 + }
  77 + }
  78 +
  79 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/internals/ConfigRepository.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.internals;
  2 +
  3 +import java.util.Properties;
  4 +
  5 +/**
  6 + * 配置 Repository 接口
  7 + *
  8 + * @author Jason Song(song_s@ctrip.com)
  9 + */
  10 +public interface ConfigRepository {
  11 +
  12 + /**
  13 + * Get the config from this repository.
  14 + * <p>
  15 + * 获得配置,以 Properties 对象返回
  16 + *
  17 + * @return config
  18 + */
  19 + Properties getConfig();
  20 +
  21 + /**
  22 + * Add change listener.
  23 + *
  24 + * @param listener the listener to observe the changes
  25 + */
  26 + void addChangeListener(RepositoryChangeListener listener);
  27 +
  28 + /**
  29 + * Remove change listener.
  30 + *
  31 + * @param listener the listener to remove
  32 + */
  33 + void removeChangeListener(RepositoryChangeListener listener);
  34 +
  35 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/internals/RemoteConfigRepository.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.internals;
  2 +
  3 +import com.google.common.base.Joiner;
  4 +import org.slf4j.Logger;
  5 +import org.slf4j.LoggerFactory;
  6 +
  7 +/**
  8 + * RemoteConfig Repository
  9 + * <p>
  10 + * 远程配置 Repository ,实现从 Config Service 拉取配置,并缓存在内存中。并且,定时 + 实时刷新缓存。
  11 + *
  12 + * @author Jason Song(song_s@ctrip.com)
  13 + */
  14 +public class RemoteConfigRepository extends AbstractConfigRepository {
  15 +
  16 + private static final Logger logger = LoggerFactory.getLogger(RemoteConfigRepository.class);
  17 + private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
  18 + private static final Joiner.MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");
  19 +
  20 + private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper();
  21 + private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();
  22 +
  23 + /**
  24 + * 远程配置长轮询服务
  25 + */
  26 + private RemoteConfigLongPollService remoteConfigLongPollService;
  27 + /**
  28 + * 指向 ApolloConfig 的 AtomicReference ,缓存配置
  29 + */
  30 + private volatile AtomicReference<ApolloConfig> m_configCache;
  31 + /**
  32 + * Namespace 名字
  33 + */
  34 + private final String m_namespace;
  35 + /**
  36 + * ScheduledExecutorService 对象
  37 + */
  38 + private final static ScheduledExecutorService m_executorService;
  39 + /**
  40 + * 指向 ServiceDTO( Config Service 信息) 的 AtomicReference
  41 + */
  42 + private AtomicReference<ServiceDTO> m_longPollServiceDto;
  43 + /**
  44 + * 指向 ApolloNotificationMessages 的 AtomicReference
  45 + */
  46 + private AtomicReference<ApolloNotificationMessages> m_remoteMessages;
  47 + /**
  48 + * 加载配置的 RateLimiter
  49 + */
  50 + private RateLimiter m_loadConfigRateLimiter;
  51 + /**
  52 + * 是否强制拉取缓存的标记
  53 + * <p>
  54 + * 若为 true ,则多一轮从 Config Service 拉取配置
  55 + * 为 true 的原因,RemoteConfigRepository 知道 Config Service 有配置刷新
  56 + */
  57 + private AtomicBoolean m_configNeedForceRefresh;
  58 + /**
  59 + * 失败定时重试策略,使用 {@link ExponentialSchedulePolicy}
  60 + */
  61 + private SchedulePolicy m_loadConfigFailSchedulePolicy;
  62 + private Gson gson;
  63 + private ConfigUtil m_configUtil;
  64 + private HttpUtil m_httpUtil;
  65 + private ConfigServiceLocator m_serviceLocator;
  66 +
  67 + static {
  68 + // 单线程池
  69 + m_executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("RemoteConfigRepository", true));
  70 + }
  71 +
  72 + /**
  73 + * Constructor.
  74 + *
  75 + * @param namespace the namespace
  76 + */
  77 + public RemoteConfigRepository(String namespace) {
  78 + m_namespace = namespace;
  79 + m_configCache = new AtomicReference<>();
  80 + m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
  81 + m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);
  82 + m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
  83 + remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
  84 + m_longPollServiceDto = new AtomicReference<>();
  85 + m_remoteMessages = new AtomicReference<>();
  86 + m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
  87 + m_configNeedForceRefresh = new AtomicBoolean(true);
  88 + m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(), m_configUtil.getOnErrorRetryInterval() * 8);
  89 + gson = new Gson();
  90 + // 尝试同步配置
  91 + super.trySync();
  92 + // 初始化定时刷新配置的任务
  93 + this.schedulePeriodicRefresh();
  94 + // 注册自己到 RemoteConfigLongPollService 中,实现配置更新的实时通知
  95 + this.scheduleLongPollingRefresh();
  96 + }
  97 +
  98 + @Override
  99 + public Properties getConfig() {
  100 + // 如果缓存为空,强制从 Config Service 拉取配置
  101 + if (m_configCache.get() == null) {
  102 + this.sync();
  103 + }
  104 + // 转换成 Properties 对象,并返回
  105 + return transformApolloConfigToProperties(m_configCache.get());
  106 + }
  107 +
  108 + @Override
  109 + public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
  110 + // remote config doesn't need upstream
  111 + }
  112 +
  113 + private void schedulePeriodicRefresh() {
  114 + logger.debug("Schedule periodic refresh with interval: {} {}", m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
  115 + // 创建定时任务,定时刷新配置
  116 + m_executorService.scheduleAtFixedRate(new Runnable() {
  117 + @Override
  118 + public void run() {
  119 + // 【TODO 6001】Tracer 日志
  120 + Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", m_namespace));
  121 + logger.debug("refresh config for namespace: {}", m_namespace);
  122 + // 尝试同步配置
  123 + trySync();
  124 + // 【TODO 6001】Tracer 日志
  125 + Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
  126 + }
  127 + }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
  128 + }
  129 +
  130 + @Override
  131 + protected synchronized void sync() {
  132 + // 【TODO 6001】Tracer 日志
  133 + Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
  134 + try {
  135 + // 获得缓存的 ApolloConfig 对象
  136 + ApolloConfig previous = m_configCache.get();
  137 + // 从 Config Service 加载 ApolloConfig 对象
  138 + ApolloConfig current = loadApolloConfig();
  139 +
  140 + // reference equals means HTTP 304
  141 + // 若不相等,说明更新了,设置到缓存中
  142 + if (previous != current) {
  143 + logger.debug("Remote Config refreshed!");
  144 + // 设置到缓存
  145 + m_configCache.set(current);
  146 + // 发布 Repository 的配置发生变化,触发对应的监听器们
  147 + super.fireRepositoryChange(m_namespace, this.getConfig());
  148 + }
  149 + // 【TODO 6001】Tracer 日志
  150 + if (current != null) {
  151 + Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()), current.getReleaseKey());
  152 + }
  153 + // 【TODO 6001】Tracer 日志
  154 + transaction.setStatus(Transaction.SUCCESS);
  155 + } catch (Throwable ex) {
  156 + // 【TODO 6001】Tracer 日志
  157 + transaction.setStatus(ex);
  158 + throw ex;
  159 + } finally {
  160 + // 【TODO 6001】Tracer 日志
  161 + transaction.complete();
  162 + }
  163 + }
  164 +
  165 + private Properties transformApolloConfigToProperties(ApolloConfig apolloConfig) {
  166 + Properties result = new Properties();
  167 + result.putAll(apolloConfig.getConfigurations());
  168 + return result;
  169 + }
  170 +
  171 + private ApolloConfig loadApolloConfig() {
  172 + // 限流
  173 + if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
  174 + // wait at most 5 seconds
  175 + try {
  176 + TimeUnit.SECONDS.sleep(5);
  177 + } catch (InterruptedException e) {
  178 + }
  179 + }
  180 + // 获得 appId cluster dataCenter 配置信息
  181 + String appId = m_configUtil.getAppId();
  182 + String cluster = m_configUtil.getCluster();
  183 + String dataCenter = m_configUtil.getDataCenter();
  184 + Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, m_namespace));
  185 + // 计算重试次数
  186 + int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1;
  187 + long onErrorSleepTime = 0; // 0 means no sleep
  188 + Throwable exception = null;
  189 + // 获得所有的 Config Service 的地址
  190 + List<ServiceDTO> configServices = getConfigServices();
  191 + String url = null;
  192 + // 循环读取配置重试次数直到成功。每一次,都会循环所有的 ServiceDTO 数组。
  193 + for (int i = 0; i < maxRetries; i++) {
  194 + // 随机所有的 Config Service 的地址
  195 + List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
  196 + Collections.shuffle(randomConfigServices);
  197 + // 优先访问通知配置变更的 Config Service 的地址。并且,获取到时,需要置空,避免重复优先访问。
  198 + // Access the server which notifies the client first
  199 + if (m_longPollServiceDto.get() != null) {
  200 + randomConfigServices.add(0, m_longPollServiceDto.getAndSet(null));
  201 + }
  202 + // 循环所有的 Config Service 的地址
  203 + for (ServiceDTO configService : randomConfigServices) {
  204 + // sleep 等待,下次从 Config Service 拉取配置
  205 + if (onErrorSleepTime > 0) {
  206 + logger.warn("Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}", onErrorSleepTime, m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, m_namespace);
  207 + try {
  208 + m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime);
  209 + } catch (InterruptedException e) {
  210 + //ignore
  211 + }
  212 + }
  213 + // 组装查询配置的地址
  214 + url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace, dataCenter, m_remoteMessages.get(), m_configCache.get());
  215 +
  216 + logger.debug("Loading config from {}", url);
  217 + // 创建 HttpRequest 对象
  218 + HttpRequest request = new HttpRequest(url);
  219 +
  220 + // 【TODO 6001】Tracer 日志
  221 + Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig");
  222 + transaction.addData("Url", url);
  223 + try {
  224 + // 发起请求,返回 HttpResponse 对象
  225 + HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class);
  226 + // 设置 m_configNeedForceRefresh = false
  227 + m_configNeedForceRefresh.set(false);
  228 + // 标记成功
  229 + m_loadConfigFailSchedulePolicy.success();
  230 +
  231 + // 【TODO 6001】Tracer 日志
  232 + transaction.addData("StatusCode", response.getStatusCode());
  233 + transaction.setStatus(Transaction.SUCCESS);
  234 +
  235 + // 无新的配置,直接返回缓存的 ApolloConfig 对象
  236 + if (response.getStatusCode() == 304) {
  237 + logger.debug("Config server responds with 304 HTTP status code.");
  238 + return m_configCache.get();
  239 + }
  240 +
  241 + // 有新的配置,进行返回新的 ApolloConfig 对象
  242 + ApolloConfig result = response.getBody();
  243 + logger.debug("Loaded config for {}: {}", m_namespace, result);
  244 + return result;
  245 + } catch (ApolloConfigStatusCodeException ex) {
  246 + ApolloConfigStatusCodeException statusCodeException = ex;
  247 + // 若返回的状态码是 404 ,说明查询配置的 Config Service 不存在该 Namespace 。
  248 + // config not found
  249 + if (ex.getStatusCode() == 404) {
  250 + String message = String.format("Could not find config for namespace - appId: %s, cluster: %s, namespace: %s, " +
  251 + "please check whether the configs are released in Apollo!", appId, cluster, m_namespace);
  252 + statusCodeException = new ApolloConfigStatusCodeException(ex.getStatusCode(), message);
  253 + }
  254 + // 【TODO 6001】Tracer 日志
  255 + Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException));
  256 + transaction.setStatus(statusCodeException);
  257 + // 设置最终的异常
  258 + exception = statusCodeException;
  259 + } catch (Throwable ex) {
  260 + // 【TODO 6001】Tracer 日志
  261 + Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
  262 + transaction.setStatus(ex);
  263 + // 设置最终的异常
  264 + exception = ex;
  265 + } finally {
  266 + // 【TODO 6001】Tracer 日志
  267 + transaction.complete();
  268 + }
  269 + // 计算延迟时间
  270 + // if force refresh, do normal sleep, if normal config load, do exponential sleep
  271 + onErrorSleepTime = m_configNeedForceRefresh.get() ? m_configUtil.getOnErrorRetryInterval() : m_loadConfigFailSchedulePolicy.fail();
  272 + }
  273 +
  274 + }
  275 + // 若查询配置失败,抛出 ApolloConfigException 异常
  276 + String message = String.format("Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s", appId, cluster, m_namespace, url);
  277 + throw new ApolloConfigException(message, exception);
  278 + }
  279 +
  280 + // 组装查询配置的地址
  281 + String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace,
  282 + String dataCenter, ApolloNotificationMessages remoteMessages, ApolloConfig previousConfig) {
  283 + String path = "configs/%s/%s/%s"; // /configs/{appId}/{clusterName}/{namespace:.+}
  284 + List<String> pathParams = Lists.newArrayList(pathEscaper.escape(appId), pathEscaper.escape(cluster), pathEscaper.escape(namespace));
  285 + Map<String, String> queryParams = Maps.newHashMap();
  286 + // releaseKey
  287 + if (previousConfig != null) {
  288 + queryParams.put("releaseKey", queryParamEscaper.escape(previousConfig.getReleaseKey()));
  289 + }
  290 + // dataCenter
  291 + if (!Strings.isNullOrEmpty(dataCenter)) {
  292 + queryParams.put("dataCenter", queryParamEscaper.escape(dataCenter));
  293 + }
  294 + // ip
  295 + String localIp = m_configUtil.getLocalIp();
  296 + if (!Strings.isNullOrEmpty(localIp)) {
  297 + queryParams.put("ip", queryParamEscaper.escape(localIp));
  298 + }
  299 + // messages
  300 + if (remoteMessages != null) {
  301 + queryParams.put("messages", queryParamEscaper.escape(gson.toJson(remoteMessages)));
  302 + }
  303 + // 格式化 URL
  304 + String pathExpanded = String.format(path, pathParams.toArray());
  305 + // 拼接 Query String
  306 + if (!queryParams.isEmpty()) {
  307 + pathExpanded += "?" + MAP_JOINER.join(queryParams);
  308 + }
  309 + // 拼接最终的请求 URL
  310 + if (!uri.endsWith("/")) {
  311 + uri += "/";
  312 + }
  313 + return uri + pathExpanded;
  314 + }
  315 +
  316 + /**
  317 + * 注册自己到 RemoteConfigLongPollService 中,实现配置更新的实时通知
  318 + */
  319 + private void scheduleLongPollingRefresh() {
  320 + remoteConfigLongPollService.submit(m_namespace, this);
  321 + }
  322 +
  323 + /**
  324 + * 当长轮询到配置更新时,发起同步配置的任务
  325 + *
  326 + * @param longPollNotifiedServiceDto ServiceDTO 对象
  327 + * @param remoteMessages ApolloNotificationMessages 对象
  328 + */
  329 + public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
  330 + // 提交同步任务
  331 + m_executorService.submit(new Runnable() {
  332 +
  333 + @Override
  334 + public void run() {
  335 + // 设置 m_configNeedForceRefresh 为 true
  336 + m_configNeedForceRefresh.set(true);
  337 + // 尝试同步配置
  338 + trySync();
  339 + }
  340 +
  341 + });
  342 + }
  343 +
  344 +
  345 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/internals/RepositoryChangeListener.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.internals;
  2 +
  3 +import java.util.Properties;
  4 +
  5 +/**
  6 + * @author Jason Song(song_s@ctrip.com)
  7 + */
  8 +public interface RepositoryChangeListener {
  9 +
  10 + /**
  11 + * Invoked when config repository changes.
  12 + *
  13 + * @param namespace the namespace of this repository change
  14 + * @param newProperties the properties after change
  15 + */
  16 + void onRepositoryChange(String namespace, Properties newProperties);
  17 +
  18 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/ApolloAnnotationProcessor.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.annotation;
  2 +
  3 +import cn.hutool.core.lang.Singleton;
  4 +import cn.iocoder.dashboard.framework.apollox.Config;
  5 +import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
  6 +import cn.iocoder.dashboard.framework.apollox.DefaultConfig;
  7 +import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
  8 +import com.google.common.base.Preconditions;
  9 +import org.springframework.core.annotation.AnnotationUtils;
  10 +import org.springframework.util.ReflectionUtils;
  11 +
  12 +import java.lang.reflect.Field;
  13 +import java.lang.reflect.Method;
  14 +
  15 +/**
  16 + * Apollo Annotation Processor for Spring Application
  17 + *
  18 + * @author Jason Song(song_s@ctrip.com)
  19 + */
  20 +public class ApolloAnnotationProcessor extends ApolloProcessor {
  21 +
  22 + @Override
  23 + protected void processField(Object bean, String beanName, Field field) {
  24 + ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
  25 + if (annotation == null) {
  26 + return;
  27 + }
  28 +
  29 + Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()), "Invalid type: %s for field: %s, should be Config", field.getType(), field);
  30 +
  31 + // 创建 Config 对象
  32 + Config config = Singleton.get(DefaultConfig.class);
  33 +
  34 + // 设置 Config 对象,到对应的 Field
  35 + ReflectionUtils.makeAccessible(field);
  36 + ReflectionUtils.setField(field, bean, config);
  37 + }
  38 +
  39 + @Override
  40 + protected void processMethod(final Object bean, String beanName, final Method method) {
  41 + ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class);
  42 + if (annotation == null) {
  43 + return;
  44 + }
  45 + Class<?>[] parameterTypes = method.getParameterTypes();
  46 + Preconditions.checkArgument(parameterTypes.length == 1, "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method);
  47 + Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]), "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method);
  48 +
  49 + // 创建 ConfigChangeListener 监听器。该监听器会调用被注解的方法。
  50 + ReflectionUtils.makeAccessible(method);
  51 + ConfigChangeListener configChangeListener = changeEvent -> {
  52 + // 反射调用
  53 + ReflectionUtils.invokeMethod(method, bean, changeEvent);
  54 + };
  55 +
  56 + // 向指定 Namespace 的 Config 对象们,注册该监听器
  57 + Config config = Singleton.get(DefaultConfig.class);
  58 + config.addChangeListener(configChangeListener);
  59 + }
  60 +
  61 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/ApolloConfig.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.annotation;
  2 +
  3 +import java.lang.annotation.*;
  4 +
  5 +/**
  6 + * Use this annotation to inject Apollo Config Instance.
  7 + *
  8 + * <p>Usage example:</p>
  9 + * <pre class="code">
  10 + * //Inject the config for "someNamespace"
  11 + * &#064;ApolloConfig("someNamespace")
  12 + * private Config config;
  13 + * </pre>
  14 + *
  15 + * @author Jason Song(song_s@ctrip.com)
  16 + */
  17 +@Retention(RetentionPolicy.RUNTIME)
  18 +@Target(ElementType.FIELD)
  19 +@Documented
  20 +public @interface ApolloConfig {
  21 +
  22 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/annotation/ApolloConfigChangeListener.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.annotation;
  2 +
  3 +import java.lang.annotation.*;
  4 +
  5 +/**
  6 + * Use this annotation to register Apollo ConfigChangeListener.
  7 + *
  8 + * @author Jason Song(song_s@ctrip.com)
  9 + */
  10 +@Retention(RetentionPolicy.RUNTIME)
  11 +@Target(ElementType.METHOD)
  12 +@Documented
  13 +public @interface ApolloConfigChangeListener {
  14 +
  15 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/boot/ApolloApplicationContextInitializer.java
1 package cn.iocoder.dashboard.framework.apollox.spring.boot; 1 package cn.iocoder.dashboard.framework.apollox.spring.boot;
2 2
3 3
4 -import cn.hutool.core.lang.Singleton;  
5 import cn.iocoder.dashboard.framework.apollox.Config; 4 import cn.iocoder.dashboard.framework.apollox.Config;
6 -import cn.iocoder.dashboard.framework.apollox.DBConfig;  
7 -import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource; 5 +import cn.iocoder.dashboard.framework.apollox.ConfigService;
  6 +import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourceFactory;
  7 +import cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants;
  8 +import cn.iocoder.dashboard.framework.apollox.spring.util.SpringInjector;
8 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
9 import org.springframework.context.ApplicationContextInitializer; 10 import org.springframework.context.ApplicationContextInitializer;
10 import org.springframework.context.ConfigurableApplicationContext; 11 import org.springframework.context.ConfigurableApplicationContext;
  12 +import org.springframework.core.env.CompositePropertySource;
11 import org.springframework.core.env.ConfigurableEnvironment; 13 import org.springframework.core.env.ConfigurableEnvironment;
12 14
  15 +import java.util.Collections;
  16 +import java.util.List;
  17 +
13 import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME; 18 import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME;
14 19
15 @Slf4j 20 @Slf4j
16 public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { 21 public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
17 22
  23 + private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
  24 +
18 @Override 25 @Override
19 public void initialize(ConfigurableApplicationContext context) { 26 public void initialize(ConfigurableApplicationContext context) {
20 ConfigurableEnvironment environment = context.getEnvironment(); 27 ConfigurableEnvironment environment = context.getEnvironment();
@@ -24,11 +31,27 @@ public class ApolloApplicationContextInitializer implements ApplicationContextIn @@ -24,11 +31,27 @@ public class ApolloApplicationContextInitializer implements ApplicationContextIn
24 return; 31 return;
25 } 32 }
26 33
27 - // 创建自定义的 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource  
28 - Config config = Singleton.get(DBConfig.class);  
29 - ConfigPropertySource configPropertySource = new ConfigPropertySource(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, config); 34 + // 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
  35 + if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
  36 + // already initialized
  37 + return;
  38 + }
  39 +
  40 + // 获得 "apollo.bootstrap.namespaces" 配置项
  41 + List<String> namespaceList = Collections.singletonList("default");
  42 +
  43 + // 按照优先级,顺序遍历 Namespace
  44 + CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
  45 + for (String namespace : namespaceList) {
  46 + // 创建 Apollo Config 对象
  47 + Config config = ConfigService.getConfig(namespace);
  48 + // 创建 Namespace 对应的 ConfigPropertySource 对象
  49 + // 添加到 `composite` 中。
  50 + composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
  51 + }
  52 +
30 // 添加到 `environment` 中,且优先级最高 53 // 添加到 `environment` 中,且优先级最高
31 - environment.getPropertySources().addFirst(configPropertySource); 54 + environment.getPropertySources().addFirst(composite);
32 } 55 }
33 56
34 } 57 }
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySource.java
1 package cn.iocoder.dashboard.framework.apollox.spring.config; 1 package cn.iocoder.dashboard.framework.apollox.spring.config;
2 2
3 import cn.iocoder.dashboard.framework.apollox.Config; 3 import cn.iocoder.dashboard.framework.apollox.Config;
  4 +import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
4 import org.springframework.core.env.EnumerablePropertySource; 5 import org.springframework.core.env.EnumerablePropertySource;
5 6
6 import java.util.Set; 7 import java.util.Set;
@@ -16,7 +17,7 @@ public class ConfigPropertySource extends EnumerablePropertySource&lt;Config&gt; { @@ -16,7 +17,7 @@ public class ConfigPropertySource extends EnumerablePropertySource&lt;Config&gt; {
16 17
17 private static final String[] EMPTY_ARRAY = new String[0]; 18 private static final String[] EMPTY_ARRAY = new String[0];
18 19
19 - public ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source` 20 + ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source`
20 super(name, source); 21 super(name, source);
21 } 22 }
22 23
@@ -36,4 +37,13 @@ public class ConfigPropertySource extends EnumerablePropertySource&lt;Config&gt; { @@ -36,4 +37,13 @@ public class ConfigPropertySource extends EnumerablePropertySource&lt;Config&gt; {
36 return this.source.getProperty(name, null); 37 return this.source.getProperty(name, null);
37 } 38 }
38 39
  40 + /**
  41 + * 添加 ConfigChangeListener 到 Config 中
  42 + *
  43 + * @param listener 监听器
  44 + */
  45 + public void addChangeListener(ConfigChangeListener listener) {
  46 + this.source.addChangeListener(listener);
  47 + }
  48 +
39 } 49 }
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySourceFactory.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.config;
  2 +
  3 +import cn.iocoder.dashboard.framework.apollox.Config;
  4 +import com.google.common.collect.Lists;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * {@link ConfigPropertySource} 工厂
  10 + */
  11 +public class ConfigPropertySourceFactory {
  12 +
  13 + /**
  14 + * ConfigPropertySource 数组
  15 + */
  16 + private final List<ConfigPropertySource> configPropertySources = Lists.newLinkedList();
  17 +
  18 + // 创建 ConfigPropertySource 对象
  19 + public ConfigPropertySource getConfigPropertySource(String name, Config source) {
  20 + // 创建 ConfigPropertySource 对象
  21 + ConfigPropertySource configPropertySource = new ConfigPropertySource(name, source);
  22 + // 添加到数组中
  23 + configPropertySources.add(configPropertySource);
  24 + return configPropertySource;
  25 + }
  26 +
  27 + public List<ConfigPropertySource> getAllConfigPropertySources() {
  28 + return Lists.newLinkedList(configPropertySources);
  29 + }
  30 +
  31 +}
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/ConfigPropertySourcesProcessor.java
1 package cn.iocoder.dashboard.framework.apollox.spring.config; 1 package cn.iocoder.dashboard.framework.apollox.spring.config;
2 2
  3 +import cn.iocoder.dashboard.framework.apollox.spring.annotation.ApolloAnnotationProcessor;
3 import cn.iocoder.dashboard.framework.apollox.spring.annotation.SpringValueProcessor; 4 import cn.iocoder.dashboard.framework.apollox.spring.annotation.SpringValueProcessor;
4 import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor; 5 import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
5 import cn.iocoder.dashboard.framework.apollox.spring.property.SpringValueDefinitionProcessor; 6 import cn.iocoder.dashboard.framework.apollox.spring.property.SpringValueDefinitionProcessor;
@@ -20,8 +21,12 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor imp @@ -20,8 +21,12 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor imp
20 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 21 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
21 // 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html 22 // 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html
22 BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class); 23 BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class);
  24 + // 注册 ApolloAnnotationProcessor 到 BeanDefinitionRegistry 中,因为 XML 配置的 Bean 对象,也可能存在 @ApolloConfig 和 @ApolloConfigChangeListener 注解。
  25 + BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class);
23 // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制 26 // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制
24 BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); 27 BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
  28 + // 注册 ApolloJsonValueProcessor 到 BeanDefinitionRegistry 中,因为 XML 配置的 Bean 对象,也可能存在 @ApolloJsonValue 注解。
  29 +// BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(), ApolloJsonValueProcessor.class); TODO 芋艿:暂时不需要迁移
25 30
26 // 处理 XML 配置的 Spring PlaceHolder 31 // 处理 XML 配置的 Spring PlaceHolder
27 processSpringValueDefinition(registry); 32 processSpringValueDefinition(registry);
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/config/PropertySourcesConstants.java
@@ -4,4 +4,6 @@ public interface PropertySourcesConstants { @@ -4,4 +4,6 @@ public interface PropertySourcesConstants {
4 4
5 String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources"; 5 String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
6 6
  7 + String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
  8 +
7 } 9 }
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/property/PropertySourcesProcessor.java
1 package cn.iocoder.dashboard.framework.apollox.spring.property; 1 package cn.iocoder.dashboard.framework.apollox.spring.property;
2 2
  3 +import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource;
  4 +import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourceFactory;
  5 +import cn.iocoder.dashboard.framework.apollox.spring.util.SpringInjector;
3 import org.springframework.beans.BeansException; 6 import org.springframework.beans.BeansException;
4 import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 7 import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
5 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 8 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -9,6 +12,7 @@ import org.springframework.core.PriorityOrdered; @@ -9,6 +12,7 @@ import org.springframework.core.PriorityOrdered;
9 import org.springframework.core.env.ConfigurableEnvironment; 12 import org.springframework.core.env.ConfigurableEnvironment;
10 import org.springframework.core.env.Environment; 13 import org.springframework.core.env.Environment;
11 14
  15 +import java.util.List;
12 import java.util.concurrent.atomic.AtomicBoolean; 16 import java.util.concurrent.atomic.AtomicBoolean;
13 17
14 /** 18 /**
@@ -28,14 +32,29 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir @@ -28,14 +32,29 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
28 */ 32 */
29 private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false); 33 private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
30 34
  35 + private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
31 /** 36 /**
32 * Spring ConfigurableEnvironment 对象 37 * Spring ConfigurableEnvironment 对象
33 */ 38 */
34 private ConfigurableEnvironment environment; 39 private ConfigurableEnvironment environment;
35 40
  41 +
36 @Override 42 @Override
37 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 43 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  44 + if (INITIALIZED.compareAndSet(false, true)) {
  45 + // 初始化 AutoUpdateConfigChangeListener 对象,实现属性的自动更新
  46 + initializeAutoUpdatePropertiesFeature(beanFactory);
  47 + }
  48 + }
38 49
  50 + private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
  51 + // 创建 AutoUpdateConfigChangeListener 对象
  52 + AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(environment, beanFactory);
  53 + // 循环,向 ConfigPropertySource 注册配置变更器
  54 + List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
  55 + for (ConfigPropertySource configPropertySource : configPropertySources) {
  56 + configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
  57 + }
39 } 58 }
40 59
41 @Override 60 @Override
src/main/java/cn/iocoder/dashboard/framework/apollox/spring/util/SpringInjector.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.apollox.spring.util;
  2 +
  3 +import cn.hutool.core.lang.Singleton;
  4 +
  5 +/**
  6 + * Spring 注入器
  7 + */
  8 +public class SpringInjector {
  9 +
  10 + public static <T> T getInstance(Class<T> clazz) {
  11 + return Singleton.get(clazz);
  12 + }
  13 +
  14 +}
src/main/java/cn/iocoder/dashboard/modules/system/controller/config/SysConfigController.java
@@ -11,6 +11,7 @@ import cn.iocoder.dashboard.modules.system.service.config.SysConfigService; @@ -11,6 +11,7 @@ import cn.iocoder.dashboard.modules.system.service.config.SysConfigService;
11 import io.swagger.annotations.Api; 11 import io.swagger.annotations.Api;
12 import io.swagger.annotations.ApiImplicitParam; 12 import io.swagger.annotations.ApiImplicitParam;
13 import io.swagger.annotations.ApiOperation; 13 import io.swagger.annotations.ApiOperation;
  14 +import org.springframework.beans.factory.annotation.Value;
14 import org.springframework.validation.annotation.Validated; 15 import org.springframework.validation.annotation.Validated;
15 import org.springframework.web.bind.annotation.*; 16 import org.springframework.web.bind.annotation.*;
16 17
@@ -27,20 +28,18 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.CO @@ -27,20 +28,18 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.CO
27 @RequestMapping("/system/config") 28 @RequestMapping("/system/config")
28 public class SysConfigController { 29 public class SysConfigController {
29 30
30 -// private SpringValueRegistry  
31 -//  
32 -// @Value("demo.test")  
33 -// private String demo;  
34 -//  
35 -// @GetMapping("/demo")  
36 -// public String demo() {  
37 -// return demo;  
38 -// }  
39 -//  
40 -// @PostMapping("/demo")  
41 -// public void setDemo() {  
42 -//  
43 -// } 31 + @Value("${demo.test:false}")
  32 + private String demo;
  33 +
  34 + @GetMapping("/demo")
  35 + public String demo() {
  36 + return demo;
  37 + }
  38 +
  39 + @PostMapping("/demo")
  40 + public void setDemo() {
  41 +
  42 + }
44 43
45 @Resource 44 @Resource
46 private SysConfigService configService; 45 private SysConfigService configService;