mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	提交下,准备简化。
This commit is contained in:
		| @@ -25,4 +25,11 @@ public interface Config { | ||||
|      */ | ||||
|     Set<String> getPropertyNames(); | ||||
|  | ||||
|     /** | ||||
|      * Add change listener to this config instance. | ||||
|      * | ||||
|      * @param listener the config change listener | ||||
|      */ | ||||
|     void addChangeListener(ConfigChangeListener listener); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,11 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox; | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
|  | ||||
| public class ConfigService { | ||||
|  | ||||
|     public static Config getConfig(String namespace) { | ||||
|         return Singleton.get(DefaultConfig.class); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,8 +1,9 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| public class DBConfig implements Config { | ||||
| public class DefaultConfig implements Config { | ||||
| 
 | ||||
|     @Override | ||||
|     public String getProperty(String key, String defaultValue) { | ||||
| @@ -11,7 +12,12 @@ public class DBConfig implements Config { | ||||
| 
 | ||||
|     @Override | ||||
|     public Set<String> getPropertyNames() { | ||||
|         return null; | ||||
|         return Collections.emptySet(); // TODO 等下实现 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void addChangeListener(ConfigChangeListener listener) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @@ -0,0 +1,79 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.internals; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Properties; | ||||
| import java.util.concurrent.CopyOnWriteArrayList; | ||||
|  | ||||
| /** | ||||
|  * 配置 Repository 抽象类 | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| public abstract class AbstractConfigRepository implements ConfigRepository { | ||||
|  | ||||
|     private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class); | ||||
|  | ||||
|     /** | ||||
|      * RepositoryChangeListener 数组 | ||||
|      */ | ||||
|     private List<RepositoryChangeListener> m_listeners = new CopyOnWriteArrayList<>(); | ||||
|  | ||||
|     /** | ||||
|      * 尝试同步 | ||||
|      * | ||||
|      * @return 是否同步成功 | ||||
|      */ | ||||
|     protected boolean trySync() { | ||||
|         try { | ||||
|             // 同步 | ||||
|             sync(); | ||||
|             // 返回同步成功 | ||||
|             return true; | ||||
|         } catch (Throwable ex) { | ||||
| //            Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); | ||||
|             logger.warn("Sync config failed, will retry. Repository {}", getClass(), ex); | ||||
|         } | ||||
|         // 返回同步失败 | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 同步配置 | ||||
|      */ | ||||
|     protected abstract void sync(); | ||||
|  | ||||
|     @Override | ||||
|     public void addChangeListener(RepositoryChangeListener listener) { | ||||
|         if (!m_listeners.contains(listener)) { | ||||
|             m_listeners.add(listener); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void removeChangeListener(RepositoryChangeListener listener) { | ||||
|         m_listeners.remove(listener); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 触发监听器们 | ||||
|      * | ||||
|      * @param namespace     Namespace 名字 | ||||
|      * @param newProperties 配置 | ||||
|      */ | ||||
|     protected void fireRepositoryChange(String namespace, Properties newProperties) { | ||||
|         // 循环 RepositoryChangeListener 数组 | ||||
|         for (RepositoryChangeListener listener : m_listeners) { | ||||
|             try { | ||||
|                 // 触发监听器 | ||||
|                 listener.onRepositoryChange(namespace, newProperties); | ||||
|             } catch (Throwable ex) { | ||||
| //                Tracer.logError(ex); | ||||
|                 logger.error("Failed to invoke repository change listener {}", listener.getClass(), ex); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,35 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.internals; | ||||
|  | ||||
| import java.util.Properties; | ||||
|  | ||||
| /** | ||||
|  * 配置 Repository 接口 | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| public interface ConfigRepository { | ||||
|  | ||||
|     /** | ||||
|      * Get the config from this repository. | ||||
|      * <p> | ||||
|      * 获得配置,以 Properties 对象返回 | ||||
|      * | ||||
|      * @return config | ||||
|      */ | ||||
|     Properties getConfig(); | ||||
|  | ||||
|     /** | ||||
|      * Add change listener. | ||||
|      * | ||||
|      * @param listener the listener to observe the changes | ||||
|      */ | ||||
|     void addChangeListener(RepositoryChangeListener listener); | ||||
|  | ||||
|     /** | ||||
|      * Remove change listener. | ||||
|      * | ||||
|      * @param listener the listener to remove | ||||
|      */ | ||||
|     void removeChangeListener(RepositoryChangeListener listener); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,345 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.internals; | ||||
|  | ||||
| import com.google.common.base.Joiner; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * RemoteConfig Repository | ||||
|  * <p> | ||||
|  * 远程配置 Repository ,实现从 Config Service 拉取配置,并缓存在内存中。并且,定时 + 实时刷新缓存。 | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| public class RemoteConfigRepository extends AbstractConfigRepository { | ||||
|  | ||||
|     private static final Logger logger = LoggerFactory.getLogger(RemoteConfigRepository.class); | ||||
|     private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR); | ||||
|     private static final Joiner.MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("="); | ||||
|  | ||||
|     private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper(); | ||||
|     private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper(); | ||||
|  | ||||
|     /** | ||||
|      * 远程配置长轮询服务 | ||||
|      */ | ||||
|     private RemoteConfigLongPollService remoteConfigLongPollService; | ||||
|     /** | ||||
|      * 指向 ApolloConfig 的 AtomicReference ,缓存配置 | ||||
|      */ | ||||
|     private volatile AtomicReference<ApolloConfig> m_configCache; | ||||
|     /** | ||||
|      * Namespace 名字 | ||||
|      */ | ||||
|     private final String m_namespace; | ||||
|     /** | ||||
|      * ScheduledExecutorService 对象 | ||||
|      */ | ||||
|     private final static ScheduledExecutorService m_executorService; | ||||
|     /** | ||||
|      * 指向 ServiceDTO( Config Service 信息) 的 AtomicReference | ||||
|      */ | ||||
|     private AtomicReference<ServiceDTO> m_longPollServiceDto; | ||||
|     /** | ||||
|      * 指向 ApolloNotificationMessages 的 AtomicReference | ||||
|      */ | ||||
|     private AtomicReference<ApolloNotificationMessages> m_remoteMessages; | ||||
|     /** | ||||
|      * 加载配置的 RateLimiter | ||||
|      */ | ||||
|     private RateLimiter m_loadConfigRateLimiter; | ||||
|     /** | ||||
|      * 是否强制拉取缓存的标记 | ||||
|      * <p> | ||||
|      * 若为 true ,则多一轮从 Config Service 拉取配置 | ||||
|      * 为 true 的原因,RemoteConfigRepository 知道 Config Service 有配置刷新 | ||||
|      */ | ||||
|     private AtomicBoolean m_configNeedForceRefresh; | ||||
|     /** | ||||
|      * 失败定时重试策略,使用 {@link ExponentialSchedulePolicy} | ||||
|      */ | ||||
|     private SchedulePolicy m_loadConfigFailSchedulePolicy; | ||||
|     private Gson gson; | ||||
|     private ConfigUtil m_configUtil; | ||||
|     private HttpUtil m_httpUtil; | ||||
|     private ConfigServiceLocator m_serviceLocator; | ||||
|  | ||||
|     static { | ||||
|         // 单线程池 | ||||
|         m_executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("RemoteConfigRepository", true)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Constructor. | ||||
|      * | ||||
|      * @param namespace the namespace | ||||
|      */ | ||||
|     public RemoteConfigRepository(String namespace) { | ||||
|         m_namespace = namespace; | ||||
|         m_configCache = new AtomicReference<>(); | ||||
|         m_configUtil = ApolloInjector.getInstance(ConfigUtil.class); | ||||
|         m_httpUtil = ApolloInjector.getInstance(HttpUtil.class); | ||||
|         m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class); | ||||
|         remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class); | ||||
|         m_longPollServiceDto = new AtomicReference<>(); | ||||
|         m_remoteMessages = new AtomicReference<>(); | ||||
|         m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS()); | ||||
|         m_configNeedForceRefresh = new AtomicBoolean(true); | ||||
|         m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(), m_configUtil.getOnErrorRetryInterval() * 8); | ||||
|         gson = new Gson(); | ||||
|         // 尝试同步配置 | ||||
|         super.trySync(); | ||||
|         // 初始化定时刷新配置的任务 | ||||
|         this.schedulePeriodicRefresh(); | ||||
|         // 注册自己到 RemoteConfigLongPollService 中,实现配置更新的实时通知 | ||||
|         this.scheduleLongPollingRefresh(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Properties getConfig() { | ||||
|         // 如果缓存为空,强制从 Config Service 拉取配置 | ||||
|         if (m_configCache.get() == null) { | ||||
|             this.sync(); | ||||
|         } | ||||
|         // 转换成 Properties 对象,并返回 | ||||
|         return transformApolloConfigToProperties(m_configCache.get()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) { | ||||
|         // remote config doesn't need upstream | ||||
|     } | ||||
|  | ||||
|     private void schedulePeriodicRefresh() { | ||||
|         logger.debug("Schedule periodic refresh with interval: {} {}", m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit()); | ||||
|         // 创建定时任务,定时刷新配置 | ||||
|         m_executorService.scheduleAtFixedRate(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 // 【TODO 6001】Tracer 日志 | ||||
|                 Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", m_namespace)); | ||||
|                 logger.debug("refresh config for namespace: {}", m_namespace); | ||||
|                 // 尝试同步配置 | ||||
|                 trySync(); | ||||
|                 // 【TODO 6001】Tracer 日志 | ||||
|                 Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION); | ||||
|             } | ||||
|         }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected synchronized void sync() { | ||||
|         // 【TODO 6001】Tracer 日志 | ||||
|         Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig"); | ||||
|         try { | ||||
|             // 获得缓存的 ApolloConfig 对象 | ||||
|             ApolloConfig previous = m_configCache.get(); | ||||
|             // 从 Config Service 加载 ApolloConfig 对象 | ||||
|             ApolloConfig current = loadApolloConfig(); | ||||
|  | ||||
|             // reference equals means HTTP 304 | ||||
|             // 若不相等,说明更新了,设置到缓存中 | ||||
|             if (previous != current) { | ||||
|                 logger.debug("Remote Config refreshed!"); | ||||
|                 // 设置到缓存 | ||||
|                 m_configCache.set(current); | ||||
|                 // 发布 Repository 的配置发生变化,触发对应的监听器们 | ||||
|                 super.fireRepositoryChange(m_namespace, this.getConfig()); | ||||
|             } | ||||
|             // 【TODO 6001】Tracer 日志 | ||||
|             if (current != null) { | ||||
|                 Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()), current.getReleaseKey()); | ||||
|             } | ||||
|             // 【TODO 6001】Tracer 日志 | ||||
|             transaction.setStatus(Transaction.SUCCESS); | ||||
|         } catch (Throwable ex) { | ||||
|             // 【TODO 6001】Tracer 日志 | ||||
|             transaction.setStatus(ex); | ||||
|             throw ex; | ||||
|         } finally { | ||||
|             // 【TODO 6001】Tracer 日志 | ||||
|             transaction.complete(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private Properties transformApolloConfigToProperties(ApolloConfig apolloConfig) { | ||||
|         Properties result = new Properties(); | ||||
|         result.putAll(apolloConfig.getConfigurations()); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private ApolloConfig loadApolloConfig() { | ||||
|         // 限流 | ||||
|         if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) { | ||||
|             // wait at most 5 seconds | ||||
|             try { | ||||
|                 TimeUnit.SECONDS.sleep(5); | ||||
|             } catch (InterruptedException e) { | ||||
|             } | ||||
|         } | ||||
|         // 获得 appId cluster dataCenter 配置信息 | ||||
|         String appId = m_configUtil.getAppId(); | ||||
|         String cluster = m_configUtil.getCluster(); | ||||
|         String dataCenter = m_configUtil.getDataCenter(); | ||||
|         Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, m_namespace)); | ||||
|         // 计算重试次数 | ||||
|         int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1; | ||||
|         long onErrorSleepTime = 0; // 0 means no sleep | ||||
|         Throwable exception = null; | ||||
|         // 获得所有的 Config Service 的地址 | ||||
|         List<ServiceDTO> configServices = getConfigServices(); | ||||
|         String url = null; | ||||
|         // 循环读取配置重试次数直到成功。每一次,都会循环所有的 ServiceDTO 数组。 | ||||
|         for (int i = 0; i < maxRetries; i++) { | ||||
|             // 随机所有的 Config Service 的地址 | ||||
|             List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices); | ||||
|             Collections.shuffle(randomConfigServices); | ||||
|             // 优先访问通知配置变更的 Config Service 的地址。并且,获取到时,需要置空,避免重复优先访问。 | ||||
|             // Access the server which notifies the client first | ||||
|             if (m_longPollServiceDto.get() != null) { | ||||
|                 randomConfigServices.add(0, m_longPollServiceDto.getAndSet(null)); | ||||
|             } | ||||
|             // 循环所有的 Config Service 的地址 | ||||
|             for (ServiceDTO configService : randomConfigServices) { | ||||
|                 // sleep 等待,下次从 Config Service 拉取配置 | ||||
|                 if (onErrorSleepTime > 0) { | ||||
|                     logger.warn("Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}", onErrorSleepTime, m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, m_namespace); | ||||
|                     try { | ||||
|                         m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime); | ||||
|                     } catch (InterruptedException e) { | ||||
|                         //ignore | ||||
|                     } | ||||
|                 } | ||||
|                 // 组装查询配置的地址 | ||||
|                 url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace, dataCenter, m_remoteMessages.get(), m_configCache.get()); | ||||
|  | ||||
|                 logger.debug("Loading config from {}", url); | ||||
|                 // 创建 HttpRequest 对象 | ||||
|                 HttpRequest request = new HttpRequest(url); | ||||
|  | ||||
|                 // 【TODO 6001】Tracer 日志 | ||||
|                 Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig"); | ||||
|                 transaction.addData("Url", url); | ||||
|                 try { | ||||
|                     // 发起请求,返回 HttpResponse 对象 | ||||
|                     HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class); | ||||
|                     // 设置 m_configNeedForceRefresh = false | ||||
|                     m_configNeedForceRefresh.set(false); | ||||
|                     // 标记成功 | ||||
|                     m_loadConfigFailSchedulePolicy.success(); | ||||
|  | ||||
|                     // 【TODO 6001】Tracer 日志 | ||||
|                     transaction.addData("StatusCode", response.getStatusCode()); | ||||
|                     transaction.setStatus(Transaction.SUCCESS); | ||||
|  | ||||
|                     // 无新的配置,直接返回缓存的 ApolloConfig 对象 | ||||
|                     if (response.getStatusCode() == 304) { | ||||
|                         logger.debug("Config server responds with 304 HTTP status code."); | ||||
|                         return m_configCache.get(); | ||||
|                     } | ||||
|  | ||||
|                     // 有新的配置,进行返回新的 ApolloConfig 对象 | ||||
|                     ApolloConfig result = response.getBody(); | ||||
|                     logger.debug("Loaded config for {}: {}", m_namespace, result); | ||||
|                     return result; | ||||
|                 } catch (ApolloConfigStatusCodeException ex) { | ||||
|                     ApolloConfigStatusCodeException statusCodeException = ex; | ||||
|                     // 若返回的状态码是 404 ,说明查询配置的 Config Service 不存在该 Namespace 。 | ||||
|                     // config not found | ||||
|                     if (ex.getStatusCode() == 404) { | ||||
|                         String message = String.format("Could not find config for namespace - appId: %s, cluster: %s, namespace: %s, " + | ||||
|                                 "please check whether the configs are released in Apollo!", appId, cluster, m_namespace); | ||||
|                         statusCodeException = new ApolloConfigStatusCodeException(ex.getStatusCode(), message); | ||||
|                     } | ||||
|                     // 【TODO 6001】Tracer 日志 | ||||
|                     Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException)); | ||||
|                     transaction.setStatus(statusCodeException); | ||||
|                     // 设置最终的异常 | ||||
|                     exception = statusCodeException; | ||||
|                 } catch (Throwable ex) { | ||||
|                     // 【TODO 6001】Tracer 日志 | ||||
|                     Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); | ||||
|                     transaction.setStatus(ex); | ||||
|                     // 设置最终的异常 | ||||
|                     exception = ex; | ||||
|                 } finally { | ||||
|                     // 【TODO 6001】Tracer 日志 | ||||
|                     transaction.complete(); | ||||
|                 } | ||||
|                 // 计算延迟时间 | ||||
|                 // if force refresh, do normal sleep, if normal config load, do exponential sleep | ||||
|                 onErrorSleepTime = m_configNeedForceRefresh.get() ? m_configUtil.getOnErrorRetryInterval() : m_loadConfigFailSchedulePolicy.fail(); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         // 若查询配置失败,抛出 ApolloConfigException 异常 | ||||
|         String message = String.format("Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s", appId, cluster, m_namespace, url); | ||||
|         throw new ApolloConfigException(message, exception); | ||||
|     } | ||||
|  | ||||
|     // 组装查询配置的地址 | ||||
|     String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace, | ||||
|                                   String dataCenter, ApolloNotificationMessages remoteMessages, ApolloConfig previousConfig) { | ||||
|         String path = "configs/%s/%s/%s"; // /configs/{appId}/{clusterName}/{namespace:.+} | ||||
|         List<String> pathParams = Lists.newArrayList(pathEscaper.escape(appId), pathEscaper.escape(cluster), pathEscaper.escape(namespace)); | ||||
|         Map<String, String> queryParams = Maps.newHashMap(); | ||||
|         // releaseKey | ||||
|         if (previousConfig != null) { | ||||
|             queryParams.put("releaseKey", queryParamEscaper.escape(previousConfig.getReleaseKey())); | ||||
|         } | ||||
|         // dataCenter | ||||
|         if (!Strings.isNullOrEmpty(dataCenter)) { | ||||
|             queryParams.put("dataCenter", queryParamEscaper.escape(dataCenter)); | ||||
|         } | ||||
|         // ip | ||||
|         String localIp = m_configUtil.getLocalIp(); | ||||
|         if (!Strings.isNullOrEmpty(localIp)) { | ||||
|             queryParams.put("ip", queryParamEscaper.escape(localIp)); | ||||
|         } | ||||
|         // messages | ||||
|         if (remoteMessages != null) { | ||||
|             queryParams.put("messages", queryParamEscaper.escape(gson.toJson(remoteMessages))); | ||||
|         } | ||||
|         // 格式化 URL | ||||
|         String pathExpanded = String.format(path, pathParams.toArray()); | ||||
|         // 拼接 Query String | ||||
|         if (!queryParams.isEmpty()) { | ||||
|             pathExpanded += "?" + MAP_JOINER.join(queryParams); | ||||
|         } | ||||
|         // 拼接最终的请求 URL | ||||
|         if (!uri.endsWith("/")) { | ||||
|             uri += "/"; | ||||
|         } | ||||
|         return uri + pathExpanded; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 注册自己到 RemoteConfigLongPollService 中,实现配置更新的实时通知 | ||||
|      */ | ||||
|     private void scheduleLongPollingRefresh() { | ||||
|         remoteConfigLongPollService.submit(m_namespace, this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 当长轮询到配置更新时,发起同步配置的任务 | ||||
|      * | ||||
|      * @param longPollNotifiedServiceDto ServiceDTO 对象 | ||||
|      * @param remoteMessages             ApolloNotificationMessages 对象 | ||||
|      */ | ||||
|     public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) { | ||||
|         // 提交同步任务 | ||||
|         m_executorService.submit(new Runnable() { | ||||
|  | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 // 设置 m_configNeedForceRefresh 为 true | ||||
|                 m_configNeedForceRefresh.set(true); | ||||
|                 // 尝试同步配置 | ||||
|                 trySync(); | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.internals; | ||||
|  | ||||
| import java.util.Properties; | ||||
|  | ||||
| /** | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| public interface RepositoryChangeListener { | ||||
|  | ||||
|     /** | ||||
|      * Invoked when config repository changes. | ||||
|      * | ||||
|      * @param namespace     the namespace of this repository change | ||||
|      * @param newProperties the properties after change | ||||
|      */ | ||||
|     void onRepositoryChange(String namespace, Properties newProperties); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,61 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.annotation; | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
| import cn.iocoder.dashboard.framework.apollox.Config; | ||||
| import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener; | ||||
| import cn.iocoder.dashboard.framework.apollox.DefaultConfig; | ||||
| import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent; | ||||
| import com.google.common.base.Preconditions; | ||||
| import org.springframework.core.annotation.AnnotationUtils; | ||||
| import org.springframework.util.ReflectionUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| /** | ||||
|  * Apollo Annotation Processor for Spring Application | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| public class ApolloAnnotationProcessor extends ApolloProcessor { | ||||
|  | ||||
|     @Override | ||||
|     protected void processField(Object bean, String beanName, Field field) { | ||||
|         ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class); | ||||
|         if (annotation == null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()), "Invalid type: %s for field: %s, should be Config", field.getType(), field); | ||||
|  | ||||
|         // 创建 Config 对象 | ||||
|         Config config = Singleton.get(DefaultConfig.class); | ||||
|  | ||||
|         // 设置 Config 对象,到对应的 Field | ||||
|         ReflectionUtils.makeAccessible(field); | ||||
|         ReflectionUtils.setField(field, bean, config); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void processMethod(final Object bean, String beanName, final Method method) { | ||||
|         ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class); | ||||
|         if (annotation == null) { | ||||
|             return; | ||||
|         } | ||||
|         Class<?>[] parameterTypes = method.getParameterTypes(); | ||||
|         Preconditions.checkArgument(parameterTypes.length == 1, "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method); | ||||
|         Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]), "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method); | ||||
|  | ||||
|         // 创建 ConfigChangeListener 监听器。该监听器会调用被注解的方法。 | ||||
|         ReflectionUtils.makeAccessible(method); | ||||
|         ConfigChangeListener configChangeListener = changeEvent -> { | ||||
|             // 反射调用 | ||||
|             ReflectionUtils.invokeMethod(method, bean, changeEvent); | ||||
|         }; | ||||
|  | ||||
|         // 向指定 Namespace 的 Config 对象们,注册该监听器 | ||||
|         Config config = Singleton.get(DefaultConfig.class); | ||||
|         config.addChangeListener(configChangeListener); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,22 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.annotation; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * Use this annotation to inject Apollo Config Instance. | ||||
|  * | ||||
|  * <p>Usage example:</p> | ||||
|  * <pre class="code"> | ||||
|  * //Inject the config for "someNamespace" | ||||
|  * @ApolloConfig("someNamespace") | ||||
|  * private Config config; | ||||
|  * </pre> | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target(ElementType.FIELD) | ||||
| @Documented | ||||
| public @interface ApolloConfig { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.annotation; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * Use this annotation to register Apollo ConfigChangeListener. | ||||
|  * | ||||
|  * @author Jason Song(song_s@ctrip.com) | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target(ElementType.METHOD) | ||||
| @Documented | ||||
| public @interface ApolloConfigChangeListener { | ||||
|  | ||||
| } | ||||
| @@ -1,20 +1,27 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.boot; | ||||
|  | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
| import cn.iocoder.dashboard.framework.apollox.Config; | ||||
| import cn.iocoder.dashboard.framework.apollox.DBConfig; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource; | ||||
| import cn.iocoder.dashboard.framework.apollox.ConfigService; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourceFactory; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.util.SpringInjector; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.context.ApplicationContextInitializer; | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
| import org.springframework.core.env.CompositePropertySource; | ||||
| import org.springframework.core.env.ConfigurableEnvironment; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME; | ||||
|  | ||||
| @Slf4j | ||||
| public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { | ||||
|  | ||||
|     private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class); | ||||
|  | ||||
|     @Override | ||||
|     public void initialize(ConfigurableApplicationContext context) { | ||||
|         ConfigurableEnvironment environment = context.getEnvironment(); | ||||
| @@ -24,11 +31,27 @@ public class ApolloApplicationContextInitializer implements ApplicationContextIn | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 创建自定义的 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource | ||||
|         Config config = Singleton.get(DBConfig.class); | ||||
|         ConfigPropertySource configPropertySource = new ConfigPropertySource(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, config); | ||||
|         // 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource | ||||
|         if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) { | ||||
|             // already initialized | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 获得 "apollo.bootstrap.namespaces" 配置项 | ||||
|         List<String> namespaceList = Collections.singletonList("default"); | ||||
|  | ||||
|         // 按照优先级,顺序遍历 Namespace | ||||
|         CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME); | ||||
|         for (String namespace : namespaceList) { | ||||
|             // 创建 Apollo Config 对象 | ||||
|             Config config = ConfigService.getConfig(namespace); | ||||
|             // 创建 Namespace 对应的 ConfigPropertySource 对象 | ||||
|             // 添加到 `composite` 中。 | ||||
|             composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config)); | ||||
|         } | ||||
|  | ||||
|         // 添加到 `environment` 中,且优先级最高 | ||||
|         environment.getPropertySources().addFirst(configPropertySource); | ||||
|         environment.getPropertySources().addFirst(composite); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.config; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.apollox.Config; | ||||
| import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener; | ||||
| import org.springframework.core.env.EnumerablePropertySource; | ||||
|  | ||||
| import java.util.Set; | ||||
| @@ -16,7 +17,7 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> { | ||||
|  | ||||
|     private static final String[] EMPTY_ARRAY = new String[0]; | ||||
|  | ||||
|     public ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source` | ||||
|     ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source` | ||||
|         super(name, source); | ||||
|     } | ||||
|  | ||||
| @@ -36,4 +37,13 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> { | ||||
|         return this.source.getProperty(name, null); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 添加 ConfigChangeListener 到 Config 中 | ||||
|      * | ||||
|      * @param listener 监听器 | ||||
|      */ | ||||
|     public void addChangeListener(ConfigChangeListener listener) { | ||||
|         this.source.addChangeListener(listener); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,31 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.config; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.apollox.Config; | ||||
| import com.google.common.collect.Lists; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * {@link ConfigPropertySource} 工厂 | ||||
|  */ | ||||
| public class ConfigPropertySourceFactory { | ||||
|  | ||||
|     /** | ||||
|      * ConfigPropertySource 数组 | ||||
|      */ | ||||
|     private final List<ConfigPropertySource> configPropertySources = Lists.newLinkedList(); | ||||
|  | ||||
|     // 创建 ConfigPropertySource 对象 | ||||
|     public ConfigPropertySource getConfigPropertySource(String name, Config source) { | ||||
|         // 创建 ConfigPropertySource 对象 | ||||
|         ConfigPropertySource configPropertySource = new ConfigPropertySource(name, source); | ||||
|         // 添加到数组中 | ||||
|         configPropertySources.add(configPropertySource); | ||||
|         return configPropertySource; | ||||
|     } | ||||
|  | ||||
|     public List<ConfigPropertySource> getAllConfigPropertySources() { | ||||
|         return Lists.newLinkedList(configPropertySources); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.config; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.annotation.ApolloAnnotationProcessor; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.annotation.SpringValueProcessor; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.property.SpringValueDefinitionProcessor; | ||||
| @@ -20,8 +21,12 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor imp | ||||
|     public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { | ||||
|         // 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html | ||||
|         BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class); | ||||
|         // 注册 ApolloAnnotationProcessor 到 BeanDefinitionRegistry 中,因为 XML 配置的 Bean 对象,也可能存在 @ApolloConfig 和 @ApolloConfigChangeListener 注解。 | ||||
|         BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class); | ||||
|         // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制 | ||||
|         BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); | ||||
|         // 注册 ApolloJsonValueProcessor 到 BeanDefinitionRegistry 中,因为 XML 配置的 Bean 对象,也可能存在 @ApolloJsonValue 注解。 | ||||
| //        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(), ApolloJsonValueProcessor.class); TODO 芋艿:暂时不需要迁移 | ||||
|  | ||||
|         // 处理 XML 配置的 Spring PlaceHolder | ||||
|         processSpringValueDefinition(registry); | ||||
|   | ||||
| @@ -4,4 +4,6 @@ public interface PropertySourcesConstants { | ||||
|  | ||||
|   String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources"; | ||||
|  | ||||
|     String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources"; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.property; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySource; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourceFactory; | ||||
| import cn.iocoder.dashboard.framework.apollox.spring.util.SpringInjector; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanFactoryPostProcessor; | ||||
| import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||||
| @@ -9,6 +12,7 @@ import org.springframework.core.PriorityOrdered; | ||||
| import org.springframework.core.env.ConfigurableEnvironment; | ||||
| import org.springframework.core.env.Environment; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
|  | ||||
| /** | ||||
| @@ -28,14 +32,29 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir | ||||
|      */ | ||||
|     private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false); | ||||
|  | ||||
|     private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class); | ||||
|     /** | ||||
|      * Spring ConfigurableEnvironment 对象 | ||||
|      */ | ||||
|     private ConfigurableEnvironment environment; | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { | ||||
|         if (INITIALIZED.compareAndSet(false, true)) { | ||||
|             // 初始化 AutoUpdateConfigChangeListener 对象,实现属性的自动更新 | ||||
|             initializeAutoUpdatePropertiesFeature(beanFactory); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) { | ||||
|         // 创建 AutoUpdateConfigChangeListener 对象 | ||||
|         AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(environment, beanFactory); | ||||
|         // 循环,向 ConfigPropertySource 注册配置变更器 | ||||
|         List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); | ||||
|         for (ConfigPropertySource configPropertySource : configPropertySources) { | ||||
|             configPropertySource.addChangeListener(autoUpdateConfigChangeListener); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -0,0 +1,14 @@ | ||||
| package cn.iocoder.dashboard.framework.apollox.spring.util; | ||||
|  | ||||
| import cn.hutool.core.lang.Singleton; | ||||
|  | ||||
| /** | ||||
|  * Spring 注入器 | ||||
|  */ | ||||
| public class SpringInjector { | ||||
|  | ||||
|     public static <T> T getInstance(Class<T> clazz) { | ||||
|         return Singleton.get(clazz); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -11,6 +11,7 @@ import cn.iocoder.dashboard.modules.system.service.config.SysConfigService; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| @@ -27,20 +28,18 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.CO | ||||
| @RequestMapping("/system/config") | ||||
| public class SysConfigController { | ||||
|  | ||||
| //    private SpringValueRegistry | ||||
| // | ||||
| //    @Value("demo.test") | ||||
| //    private String demo; | ||||
| // | ||||
| //    @GetMapping("/demo") | ||||
| //    public String demo() { | ||||
| //        return demo; | ||||
| //    } | ||||
| // | ||||
| //    @PostMapping("/demo") | ||||
| //    public void setDemo() { | ||||
| // | ||||
| //    } | ||||
|     @Value("${demo.test:false}") | ||||
|     private String demo; | ||||
|  | ||||
|     @GetMapping("/demo") | ||||
|     public String demo() { | ||||
|         return demo; | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/demo") | ||||
|     public void setDemo() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Resource | ||||
|     private SysConfigService configService; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV