提交下,准备简化。

pull/2/head
YunaiV 2021-01-20 01:15:05 +08:00
parent ec9ed896f9
commit 6bed7778e8
18 changed files with 726 additions and 24 deletions

View File

@ -25,4 +25,11 @@ public interface Config {
*/ */
Set<String> getPropertyNames(); Set<String> getPropertyNames();
/**
* Add change listener to this config instance.
*
* @param listener the config change listener
*/
void addChangeListener(ConfigChangeListener listener);
} }

View File

@ -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);
}
}

View File

@ -1,8 +1,9 @@
package cn.iocoder.dashboard.framework.apollox; package cn.iocoder.dashboard.framework.apollox;
import java.util.Collections;
import java.util.Set; import java.util.Set;
public class DBConfig implements Config { public class DefaultConfig implements Config {
@Override @Override
public String getProperty(String key, String defaultValue) { public String getProperty(String key, String defaultValue) {
@ -11,7 +12,12 @@ public class DBConfig implements Config {
@Override @Override
public Set<String> getPropertyNames() { public Set<String> getPropertyNames() {
return null; return Collections.emptySet(); // TODO 等下实现
}
@Override
public void addChangeListener(ConfigChangeListener listener) {
} }
} }

View File

@ -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);
}
}
}
}

View File

@ -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);
}

View File

@ -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();
}
});
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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"
* &#064;ApolloConfig("someNamespace")
* private Config config;
* </pre>
*
* @author Jason Song(song_s@ctrip.com)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ApolloConfig {
}

View File

@ -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 {
}

View File

@ -1,20 +1,27 @@
package cn.iocoder.dashboard.framework.apollox.spring.boot; 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.Config;
import cn.iocoder.dashboard.framework.apollox.DBConfig; import cn.iocoder.dashboard.framework.apollox.ConfigService;
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.config.PropertySourcesConstants;
import cn.iocoder.dashboard.framework.apollox.spring.util.SpringInjector;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment; 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; import static cn.iocoder.dashboard.framework.apollox.spring.config.PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME;
@Slf4j @Slf4j
public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
@Override @Override
public void initialize(ConfigurableApplicationContext context) { public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment(); ConfigurableEnvironment environment = context.getEnvironment();
@ -24,11 +31,27 @@ public class ApolloApplicationContextInitializer implements ApplicationContextIn
return; return;
} }
// 创建自定义的 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource // 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
Config config = Singleton.get(DBConfig.class); if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
ConfigPropertySource configPropertySource = new ConfigPropertySource(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, config); // 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` 中,且优先级最高
environment.getPropertySources().addFirst(configPropertySource); environment.getPropertySources().addFirst(composite);
} }
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.dashboard.framework.apollox.spring.config; package cn.iocoder.dashboard.framework.apollox.spring.config;
import cn.iocoder.dashboard.framework.apollox.Config; import cn.iocoder.dashboard.framework.apollox.Config;
import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.EnumerablePropertySource;
import java.util.Set; import java.util.Set;
@ -16,7 +17,7 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> {
private static final String[] EMPTY_ARRAY = new String[0]; 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); super(name, source);
} }
@ -36,4 +37,13 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> {
return this.source.getProperty(name, null); return this.source.getProperty(name, null);
} }
/**
* ConfigChangeListener Config
*
* @param listener
*/
public void addChangeListener(ConfigChangeListener listener) {
this.source.addChangeListener(listener);
}
} }

View File

@ -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);
}
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.dashboard.framework.apollox.spring.config; 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.annotation.SpringValueProcessor;
import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor; import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
import cn.iocoder.dashboard.framework.apollox.spring.property.SpringValueDefinitionProcessor; 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 { public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html // 注册 PropertySourcesPlaceholderConfigurer 到 BeanDefinitionRegistry 中,替换 PlaceHolder 为对应的属性值,参考文章 https://leokongwq.github.io/2016/12/28/spring-PropertyPlaceholderConfigurer.html
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class); 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 自动更新机制 // 注册 SpringValueProcessor 到 BeanDefinitionRegistry 中,用于 PlaceHolder 自动更新机制
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); 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 // 处理 XML 配置的 Spring PlaceHolder
processSpringValueDefinition(registry); processSpringValueDefinition(registry);

View File

@ -4,4 +4,6 @@ public interface PropertySourcesConstants {
String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources"; String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
} }

View File

@ -1,5 +1,8 @@
package cn.iocoder.dashboard.framework.apollox.spring.property; 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.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 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.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; 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 static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector.getInstance(ConfigPropertySourceFactory.class);
/** /**
* Spring ConfigurableEnvironment * Spring ConfigurableEnvironment
*/ */
private ConfigurableEnvironment environment; private ConfigurableEnvironment environment;
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 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 @Override

View File

@ -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);
}
}

View File

@ -11,6 +11,7 @@ import cn.iocoder.dashboard.modules.system.service.config.SysConfigService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -27,20 +28,18 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.CO
@RequestMapping("/system/config") @RequestMapping("/system/config")
public class SysConfigController { public class SysConfigController {
// private SpringValueRegistry @Value("${demo.test:false}")
// private String demo;
// @Value("demo.test")
// private String demo; @GetMapping("/demo")
// public String demo() {
// @GetMapping("/demo") return demo;
// public String demo() { }
// return demo;
// } @PostMapping("/demo")
// public void setDemo() {
// @PostMapping("/demo")
// public void setDemo() { }
//
// }
@Resource @Resource
private SysConfigService configService; private SysConfigService configService;