Commit 6bed7778e843c56c5163870797d610afcfabd200
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
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 | + * @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<Config> { | @@ -16,7 +17,7 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> { | ||
| 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<Config> { | @@ -36,4 +37,13 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> { | ||
| 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
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; |