提交下,准备简化。
parent
ec9ed896f9
commit
6bed7778e8
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue