增加 apollo 读取本地 db,需要进一步优化~

pull/2/head
YunaiV 2021-01-21 00:52:25 +08:00
parent c4c614592a
commit 17cb37f577
35 changed files with 94 additions and 1973 deletions

View File

@ -1,5 +1,8 @@
package cn.iocoder.dashboard.framework.apollo.internals;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.config.SysConfigDO;
import com.ctrip.framework.apollo.Apollo;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
@ -8,13 +11,20 @@ import com.ctrip.framework.apollo.internals.AbstractConfigRepository;
import com.ctrip.framework.apollo.internals.ConfigRepository;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.factory.PropertiesFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
public class DBConfigRepository extends AbstractConfigRepository {
@ -27,15 +37,35 @@ public class DBConfigRepository extends AbstractConfigRepository {
}
private final ConfigUtil m_configUtil;
private final AtomicReference<Properties> m_configCache;
private PropertiesFactory propertiesFactory;
private final String m_namespace;
/**
* 使 Properties
*/
private volatile Properties m_configCache;
/**
*
*/
private volatile Date maxUpdateTime;
/**
* Spring JDBC
*/
private final JdbcTemplate jdbcTemplate;
public DBConfigRepository(String namespace) {
// 初始化变量
this.m_namespace = namespace;
m_configCache = new AtomicReference<>();
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
this.propertiesFactory = ApolloInjector.getInstance(PropertiesFactory.class);
this.m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
// TODO 优化到配置
String url = "jdbc:mysql://127.0.1:33061/ruoyi-vue-pro?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT";
String username = "root";
String password = "123456";
// DataSource dataSource = DataSourceBuilder.create().url(url).username(username).password(password).build();
DataSource dataSource = new DriverManagerDataSource(url, username, password);
this.jdbcTemplate = new JdbcTemplate(dataSource);
// 初始化加载
this.trySync();
@ -43,27 +73,32 @@ public class DBConfigRepository extends AbstractConfigRepository {
this.schedulePeriodicRefresh();
}
private AtomicInteger index = new AtomicInteger();
@Override
protected void sync() {
System.out.println("我同步啦");
// 第一步,尝试获取配置
List<SysConfigDO> configs = this.loadConfigIfUpdate(this.maxUpdateTime);
if (CollUtil.isEmpty(configs)) { // 如果没有更新,则返回
return;
}
log.info("[sync][同步到新配置,配置数量为:{}]", configs.size());
index.incrementAndGet();
Properties properties = new Properties();
properties.setProperty("demo.test", String.valueOf(index.get()));
m_configCache.set(properties);
super.fireRepositoryChange(m_namespace, properties);
// 第二步,构建新的 Properties
Properties newProperties = this.buildProperties(configs);
this.m_configCache = newProperties;
// 第三步,获取最大的配置时间
this.maxUpdateTime = configs.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
// 第四部,触发配置刷新!重要!!!!
super.fireRepositoryChange(m_namespace, newProperties);
}
@Override
public Properties getConfig() {
// 兜底,避免可能存在配置为 null 的情况
if (m_configCache.get() == null) {
if (m_configCache == null) {
this.trySync();
}
// 返回配置
return m_configCache.get();
return m_configCache;
}
@Override
@ -76,6 +111,15 @@ public class DBConfigRepository extends AbstractConfigRepository {
return ConfigSourceType.REMOTE;
}
private Properties buildProperties(List<SysConfigDO> configs) {
Properties properties = propertiesFactory.getPropertiesInstance();
configs.stream().filter(config -> 0 == config.getDeleted()) // 过滤掉被删除的配置
.forEach(config -> properties.put(config.getKey(), config.getValue()));
return properties;
}
// ========== 定时器相关操作 ==========
private void schedulePeriodicRefresh() {
log.debug("Schedule periodic refresh with interval: {} {}",
m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
@ -83,7 +127,7 @@ public class DBConfigRepository extends AbstractConfigRepository {
Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", m_namespace));
log.debug("refresh config for namespace: {}", m_namespace);
// 执行同步
// 执行同步. 内部已经 try catch 掉异常,无需在处理
trySync();
Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
@ -92,4 +136,35 @@ public class DBConfigRepository extends AbstractConfigRepository {
// TimeUnit.SECONDS);
}
// ========== 数据库相关操作 ==========
/**
*
*
*
* @param maxUpdateTime
* @return
*/
private List<SysConfigDO> loadConfigIfUpdate(Date maxUpdateTime) {
// 第一步,判断是否要更新。
boolean isUpdate = maxUpdateTime == null; // 如果更新时间为空,说明 DB 一定有新数据
if (!isUpdate) {
isUpdate = this.existsNewConfig(maxUpdateTime); // 判断数据库中是否有更新的配置
}
if (!isUpdate) {
return null;
}
// 第二步,如果有更新,则从数据库加载所有配置
return this.getSysConfigList();
}
private boolean existsNewConfig(Date maxUpdateTime) {
return jdbcTemplate.query("SELECT id FROM sys_config WHERE update_time > ? LIMIT 1",
ResultSet::next, maxUpdateTime);
}
private List<SysConfigDO> getSysConfigList() {
return jdbcTemplate.query("SELECT `key`, `value`, update_time, deleted FROM sys_config", new BeanPropertyRowMapper<>(SysConfigDO.class));
}
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.dashboard.framework.apollox;
import java.util.Set;
/**
*
*
* @author Jason Song(song_s@ctrip.com)
*/
public interface Config {
/**
* Return the property value with the given key, or {@code defaultValue} if the key doesn't exist.
*
* @param key the property name
* @param defaultValue the default value when key is not found or any error occurred
* @return the property value
*/
String getProperty(String key, String defaultValue);
/**
* Return a set of the property names
*
* @return the property names
*/
Set<String> getPropertyNames();
/**
* Add change listener to this config instance.
*
* @param listener the config change listener
*/
void addChangeListener(ConfigChangeListener listener);
}

View File

@ -1,19 +0,0 @@
package cn.iocoder.dashboard.framework.apollox;
import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
/**
* {@link Config}
*
* @author Jason Song(song_s@ctrip.com)
*/
public interface ConfigChangeListener {
/**
* Invoked when there is any config change for the namespace.
*
* @param changeEvent the event for this change
*/
void onChange(ConfigChangeEvent changeEvent);
}

View File

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

View File

@ -1,14 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.enums;
/**
*
*
* @author Jason Song(song_s@ctrip.com)
*/
public enum PropertyChangeType {
ADDED, // 添加
MODIFIED, // 修改
DELETED // 删除
}

View File

@ -1,79 +0,0 @@
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

@ -1,35 +0,0 @@
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

@ -1,345 +0,0 @@
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

@ -1,18 +0,0 @@
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

@ -1,35 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.model;
import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Holds the information for a config change.
*
*
* @author Jason Song(song_s@ctrip.com)
*/
@Data
@AllArgsConstructor
public class ConfigChange {
/**
*
*/
private final String propertyName;
/**
*
*/
private String oldValue;
/**
*
*/
private String newValue;
/**
*
*/
private PropertyChangeType changeType;
}

View File

@ -1,53 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.model;
import lombok.AllArgsConstructor;
import java.util.Map;
import java.util.Set;
/**
* A change event when a namespace's config is changed.
*
* @author Jason Song(song_s@ctrip.com)
*/
@AllArgsConstructor
public class ConfigChangeEvent {
/**
*
*
* KEY
* VALUE
*/
private final Map<String, ConfigChange> m_changes;
/**
* Get the keys changed.
*
* @return the list of the keys
*/
public Set<String> changedKeys() {
return m_changes.keySet();
}
/**
* Get a specific change instance for the key specified.
*
* @param key the changed key
* @return the change instance
*/
public ConfigChange getChange(String key) {
return m_changes.get(key);
}
/**
* Check whether the specified key is changed
*
* @param key the key
* @return true if the key is changed, false otherwise.
*/
public boolean isChanged(String key) {
return m_changes.containsKey(key);
}
}

View File

@ -1,16 +0,0 @@
/**
* Apollo Client ApolloX
*
* 使 {@link cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.config.SysConfigDO}
*
*
* 1. 使 SysConfigDO
* 2. 使 Spring @Value
* 3. SysConfigDO @Value
*
* Apollo 便
*
*
* 1.
*/
package cn.iocoder.dashboard.framework.apollox;

View File

@ -1,61 +0,0 @@
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

@ -1,22 +0,0 @@
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

@ -1,15 +0,0 @@
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,68 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.annotation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
/**
* Apollo Spring Bean
*
* Create by zhangzheng on 2018/2/6
*/
public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class<?> clazz = bean.getClass();
// 处理所有 Field
for (Field field : findAllField(clazz)) {
processField(bean, beanName, field);
}
// 处理所有的 Method
for (Method method : findAllMethod(clazz)) {
processMethod(bean, beanName, method);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* subclass should implement this method to process field
*/
protected abstract void processField(Object bean, String beanName, Field field);
/**
* subclass should implement this method to process method
*/
protected abstract void processMethod(Object bean, String beanName, Method method);
@Override
public int getOrder() {
// make it as late as possible
return Ordered.LOWEST_PRECEDENCE; // 最高优先级
}
private List<Field> findAllField(Class<?> clazz) {
final List<Field> res = new LinkedList<>();
ReflectionUtils.doWithFields(clazz, res::add);
return res;
}
private List<Method> findAllMethod(Class<?> clazz) {
final List<Method> res = new LinkedList<>();
ReflectionUtils.doWithMethods(clazz, res::add);
return res;
}
}

View File

@ -1,141 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.annotation;
import cn.hutool.core.lang.Singleton;
import cn.iocoder.dashboard.framework.apollox.spring.property.*;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
/**
* Spring value processor of field or method which has @Value and xml config placeholders.
*
* Spring Value
*
* 1. `@Value` Field Method
* 2. XML Bean PlaceHolder
*
* FieldMethodXML PlaceHolder SpringValue SpringValueRegistry
*
* PlaceHolder
*
* @author github.com/zhegexiaohuozi seimimaster@gmail.com
* @since 2017/12/20.
*/
@Slf4j
public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor {
/**
* SpringValueDefinition
*
* KEYbeanName
* VALUESpringValueDefinition
*/
private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
private final SpringValueRegistry springValueRegistry = Singleton.get(SpringValueRegistry.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions();
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 处理 Field 和 Method
super.postProcessBeforeInitialization(bean, beanName);
// 处理 XML 配置的 Bean 的 PlaceHolder 们
processBeanPropertyValues(bean, beanName);
return bean;
}
@Override
protected void processField(Object bean, String beanName, Field field) {
// register @Value on field
Value value = field.getAnnotation(Value.class);
if (value == null) {
return;
}
// 提取 `keys` 属性们。
Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (keys.isEmpty()) {
return;
}
// 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
springValueRegistry.register(key, springValue);
log.debug("Monitoring {}", springValue);
}
}
@Override
protected void processMethod(Object bean, String beanName, Method method) {
// register @Value on method
Value value = method.getAnnotation(Value.class);
if (value == null) {
return;
}
// 忽略 @Bean 注解的方法
// skip Configuration bean methods
if (method.getAnnotation(Bean.class) != null) {
return;
}
// 忽略非 setting 方法
if (method.getParameterTypes().length != 1) {
log.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
return;
}
// 提取 `keys` 属性们。
Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (keys.isEmpty()) {
return;
}
// 循环 `keys` ,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
springValueRegistry.register(key, springValue);
log.info("Monitoring {}", springValue);
}
}
private void processBeanPropertyValues(Object bean, String beanName) {
// 获得 SpringValueDefinition 数组
Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions.get(beanName);
if (propertySpringValues == null || propertySpringValues.isEmpty()) {
return;
}
// 循环 SpringValueDefinition 数组,创建对应的 SpringValue 对象,并添加到 `springValueRegistry` 中。
for (SpringValueDefinition definition : propertySpringValues) {
try {
PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
Method method = pd.getWriteMethod();
if (method == null) {
continue;
}
SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), bean, beanName, method, false);
springValueRegistry.register(definition.getKey(), springValue);
log.debug("Monitoring {}", springValue);
} catch (Throwable ex) {
log.error("Failed to enable auto update feature for {}.{}", bean.getClass(), definition.getPropertyName());
}
}
// clear
// 移除 Bean 对应的 SpringValueDefinition 数组
beanName2SpringValueDefinitions.removeAll(beanName);
}
}

View File

@ -1,57 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.boot;
import cn.iocoder.dashboard.framework.apollox.Config;
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();
// 忽略,若已经有 APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME 的 PropertySource
if (environment.getPropertySources().contains(APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
// already initialized
return;
}
// 忽略,若已经有 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(composite);
}
}

View File

@ -1,18 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.boot;
import cn.iocoder.dashboard.framework.apollox.spring.config.ConfigPropertySourcesProcessor;
import cn.iocoder.dashboard.framework.apollox.spring.property.PropertySourcesProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnMissingBean(PropertySourcesProcessor.class) // 缺失 PropertySourcesProcessor 时
public class ApolloAutoConfiguration {
@Bean
public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
return new ConfigPropertySourcesProcessor(); // 注入 ConfigPropertySourcesProcessor bean 对象
}
}

View File

@ -1,49 +0,0 @@
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;
/**
* Property source wrapper for Config
*
* {@link Config} PropertySource
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigPropertySource extends EnumerablePropertySource<Config> {
private static final String[] EMPTY_ARRAY = new String[0];
ConfigPropertySource(String name, Config source) { // 此处的 Apollo Config 作为 `source`
super(name, source);
}
@Override
public String[] getPropertyNames() {
// 从 Config 中,获得属性名集合
Set<String> propertyNames = this.source.getPropertyNames();
// 转换成 String 数组,返回
if (propertyNames.isEmpty()) {
return EMPTY_ARRAY;
}
return propertyNames.toArray(new String[0]);
}
@Override
public Object getProperty(String name) {
return this.source.getProperty(name, null);
}
/**
* ConfigChangeListener Config
*
* @param listener
*/
public void addChangeListener(ConfigChangeListener listener) {
this.source.addChangeListener(listener);
}
}

View File

@ -1,31 +0,0 @@
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,47 +0,0 @@
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;
import cn.iocoder.dashboard.framework.apollox.spring.util.BeanRegistrationUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
/**
* Apollo Property Sources processor for Spring XML Based Application
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
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);
}
/**
* For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be
* instantiated if it is added in postProcessBeanDefinitionRegistry phase, so we have to manually
* call the postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
*/
private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
// 创建 SpringValueDefinitionProcessor 对象
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
// 处理 XML 配置的 Spring PlaceHolder
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
}
}

View File

@ -1,9 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.config;
public interface PropertySourcesConstants {
String APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME = "ApolloBootstrapPropertySources";
String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
}

View File

@ -1,159 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.property;
import cn.hutool.core.lang.Singleton;
import cn.iocoder.dashboard.framework.apollox.ConfigChangeListener;
import cn.iocoder.dashboard.framework.apollox.enums.PropertyChangeType;
import cn.iocoder.dashboard.framework.apollox.model.ConfigChange;
import cn.iocoder.dashboard.framework.apollox.model.ConfigChangeEvent;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
/**
*
*
* Create by zhangzheng on 2018/3/6
*/
public class AutoUpdateConfigChangeListener implements ConfigChangeListener {
private static final Logger logger = LoggerFactory.getLogger(AutoUpdateConfigChangeListener.class);
/**
* {@link TypeConverter#convertIfNecessary(Object, Class, Field)} Field Spring 3.2.0+
*/
private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
private final Environment environment;
private final ConfigurableBeanFactory beanFactory;
/**
* TypeConverter https://blog.csdn.net/rulerp2014/article/details/51100857
*/
private final TypeConverter typeConverter;
private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory) {
this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
this.beanFactory = beanFactory;
this.typeConverter = this.beanFactory.getTypeConverter();
this.environment = environment;
this.placeholderHelper = Singleton.get(PlaceholderHelper.class);
this.springValueRegistry = Singleton.get(SpringValueRegistry.class);
}
@Override
public void onChange(ConfigChangeEvent changeEvent) {
// 获得更新的 KEY 集合
Set<String> keys = changeEvent.changedKeys();
if (CollectionUtils.isEmpty(keys)) {
return;
}
// 循环 KEY 集合,更新 StringValue
for (String key : keys) {
// 忽略,若不在 SpringValueRegistry 中
// 1. check whether the changed key is relevant
Collection<SpringValue> targetValues = springValueRegistry.get(key);
if (targetValues == null || targetValues.isEmpty()) {
continue;
}
// 校验是否需要更新
// 2. check whether the value is really changed or not (since spring property sources have hierarchies)
if (!shouldTriggerAutoUpdate(changeEvent, key)) {
continue;
}
// 循环,更新 SpringValue
// 3. update the value
for (SpringValue val : targetValues) {
updateSpringValue(val);
}
}
}
/**
* Check whether we should trigger the auto update or not.
* <br />
* For added or modified keys, we should trigger auto update if the current value in Spring equals to the new value.
* <br />
* For deleted keys, we will trigger auto update anyway.
*/
private boolean shouldTriggerAutoUpdate(ConfigChangeEvent changeEvent, String changedKey) {
ConfigChange configChange = changeEvent.getChange(changedKey);
// 若变更类型为删除,需要触发更新
if (configChange.getChangeType() == PropertyChangeType.DELETED) {
return true;
}
// 若变更类型为新增或修改,判断 environment 的值是否和最新值相等。
// 【高能】!!!
return Objects.equals(environment.getProperty(changedKey), configChange.getNewValue());
}
private void updateSpringValue(SpringValue springValue) {
try {
// 解析值
Object value = resolvePropertyValue(springValue);
// 更新 StringValue
springValue.update(value);
logger.info("Auto update apollo changed value successfully, new value: {}, {}", value, springValue);
} catch (Throwable ex) {
logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
}
}
/**
* Logic transplanted from DefaultListableBeanFactory
*
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, String, Set, TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
// value will never be null, as @Value and @ApolloJsonValue will not allow that
Object value = placeholderHelper.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
// 如果值数据结构是 JSON 类型,则使用 Gson 解析成对应值的类型
if (springValue.isJson()) {
value = parseJsonValue((String) value, springValue.getGenericType());
} else {
// 如果类型为 Field
if (springValue.isField()) {
// org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
if (typeConverterHasConvertIfNecessaryWithFieldParameter) {
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField());
} else {
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType());
}
// 如果类型为 Method
} else {
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getMethodParameter());
}
}
return value;
}
private Object parseJsonValue(String json, Type targetType) {
try {
return JSON.parseObject(json, targetType);
} catch (Throwable ex) {
logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
}
private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
try {
TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class);
} catch (Throwable ex) {
return false;
}
return true;
}
}

View File

@ -1,160 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.property;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.util.StringUtils;
import java.util.Set;
import java.util.Stack;
/**
* Placeholder
*
* Placeholder helper functions.
*/
public class PlaceholderHelper {
private static final String PLACEHOLDER_PREFIX = "${";
private static final String PLACEHOLDER_SUFFIX = "}";
private static final String VALUE_SEPARATOR = ":";
private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
private static final String EXPRESSION_PREFIX = "#{";
private static final String EXPRESSION_SUFFIX = "}";
/**
* Resolve placeholder property values, e.g.
*
* "${somePropertyValue}" -> "the actual property value"
*/
public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
// resolve string value
String strVal = beanFactory.resolveEmbeddedValue(placeholder);
// 获得 BeanDefinition 对象
BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory.getMergedBeanDefinition(beanName) : null);
// resolve expressions like "#{systemProperties.myProp}"
return evaluateBeanDefinitionString(beanFactory, strVal, bd);
}
private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value, BeanDefinition beanDefinition) {
if (beanFactory.getBeanExpressionResolver() == null) {
return value;
}
Scope scope = (beanDefinition != null ? beanFactory.getRegisteredScope(beanDefinition.getScope()) : null);
return beanFactory.getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(beanFactory, scope));
}
/**
* Extract keys from placeholder, e.g.
* <ul>
* <li>${some.key} => "some.key"</li>
* <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
* <li>${${some.key}} => "some.key"</li>
* <li>${${some.key:other.key}} => "some.key"</li>
* <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
* <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
* </ul>
*/
public Set<String> extractPlaceholderKeys(String propertyString) {
Set<String> placeholderKeys = Sets.newHashSet();
if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {
return placeholderKeys;
}
Stack<String> stack = new Stack<>();
stack.push(propertyString);
while (!stack.isEmpty()) {
String strVal = stack.pop();
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
if (startIndex == -1) {
placeholderKeys.add(strVal);
continue;
}
int endIndex = findPlaceholderEndIndex(strVal, startIndex);
if (endIndex == -1) {
// invalid placeholder?
continue;
}
String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
// ${some.key:other.key}
if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
stack.push(placeholderCandidate);
} else {
// some.key:${some.other.key:100}
int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);
if (separatorIndex == -1) {
stack.push(placeholderCandidate);
} else {
stack.push(placeholderCandidate.substring(0, separatorIndex));
String defaultValuePart =
normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
if (!Strings.isNullOrEmpty(defaultValuePart)) {
stack.push(defaultValuePart);
}
}
}
// has remaining part, e.g. ${a}.${b}
if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
if (!Strings.isNullOrEmpty(remainingPart)) {
stack.push(remainingPart);
}
}
}
return placeholderKeys;
}
private boolean isNormalizedPlaceholder(String propertyString) {
return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX);
}
private boolean isExpressionWithPlaceholder(String propertyString) {
return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX)
&& propertyString.contains(PLACEHOLDER_PREFIX);
}
private String normalizeToPlaceholder(String strVal) {
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
if (startIndex == -1) {
return null;
}
int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
if (endIndex == -1) {
return null;
}
return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
}
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + PLACEHOLDER_PREFIX.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + PLACEHOLDER_SUFFIX.length();
} else {
return index;
}
} else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
withinNestedPlaceholder++;
index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
} else {
index++;
}
}
return -1;
}
}

View File

@ -1,72 +0,0 @@
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;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered;
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;
/**
* Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br />
* <p>
* The reason why PropertySourcesProcessor implements {@link BeanFactoryPostProcessor} instead of
* {@link org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor} is that lower versions of
* Spring (e.g. 3.1.1) doesn't support registering BeanDefinitionRegistryPostProcessor in ImportBeanDefinitionRegistrar
* - {@link com.ctrip.framework.apollo.spring.annotation.ApolloConfigRegistrar}
*
* @author Jason Song(song_s@ctrip.com)
*/
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
/**
*
*/
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
public void setEnvironment(Environment environment) {
//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
this.environment = (ConfigurableEnvironment) environment;
}
@Override
public int getOrder() {
// make it as early as possible
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
}
}

View File

@ -1,152 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.property;
import org.springframework.core.MethodParameter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
/**
* Spring @Value method info
*
* @author github.com/zhegexiaohuozi seimimaster@gmail.com
* @since 2018/2/6.
*/
public class SpringValue {
/**
* Bean
*/
private Object bean;
/**
* Bean
*/
private String beanName;
/**
* Spring
*/
private MethodParameter methodParameter;
/**
* Field
*/
private Field field;
/**
* KEY
*
* Config KEY
*/
private String key;
/**
*
*/
private String placeholder;
/**
*
*/
private Class<?> targetType;
/**
* JSON
*/
private boolean isJson;
/**
* JSON 使
*/
private Type genericType;
// Field
public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) {
this.bean = bean;
this.beanName = beanName;
// Field
this.field = field;
this.key = key;
this.placeholder = placeholder;
// Field 差异
this.targetType = field.getType();
this.isJson = isJson;
if (isJson) {
this.genericType = field.getGenericType();
}
}
// Method
public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) {
this.bean = bean;
this.beanName = beanName;
// Method
this.methodParameter = new MethodParameter(method, 0);
this.key = key;
this.placeholder = placeholder;
// Method 差异
Class<?>[] paramTps = method.getParameterTypes();
this.targetType = paramTps[0];
this.isJson = isJson;
if (isJson) {
this.genericType = method.getGenericParameterTypes()[0];
}
}
public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
// Field
if (isField()) {
injectField(newVal);
// Method
} else {
injectMethod(newVal);
}
}
private void injectField(Object newVal) throws IllegalAccessException {
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(bean, newVal);
field.setAccessible(accessible);
}
private void injectMethod(Object newVal) throws InvocationTargetException, IllegalAccessException {
methodParameter.getMethod().invoke(bean, newVal);
}
public String getBeanName() {
return beanName;
}
public Class<?> getTargetType() {
return targetType;
}
public String getPlaceholder() {
return this.placeholder;
}
public MethodParameter getMethodParameter() {
return methodParameter;
}
public boolean isField() {
return this.field != null;
}
public Field getField() {
return field;
}
public Type getGenericType() {
return genericType;
}
public boolean isJson() {
return isJson;
}
@Override
public String toString() {
if (isField()) {
return String.format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass().getName(), field.getName());
}
return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
methodParameter.getMethod().getName());
}
}

View File

@ -1,28 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.property;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Spring Value
*/
@Getter
@AllArgsConstructor
public class SpringValueDefinition {
/**
* KEY
*
* Config KEY
*/
private final String key;
/**
*
*/
private final String placeholder;
/**
*
*/
private final String propertyName;
}

View File

@ -1,94 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.property;
import cn.hutool.core.lang.Singleton;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* To process xml config placeholders, e.g.
*
* <pre>
* &lt;bean class=&quot;com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean&quot;&gt;
* &lt;property name=&quot;timeout&quot; value=&quot;${timeout:200}&quot;/&gt;
* &lt;property name=&quot;batch&quot; value=&quot;${batch:100}&quot;/&gt;
* &lt;/bean&gt;
* </pre>
*/
public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
/**
* SpringValueDefinition
* <p>
* KEYbeanName
* VALUESpringValueDefinition
*/
private static final Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
/**
*
*/
private static final AtomicBoolean initialized = new AtomicBoolean(false);
private final PlaceholderHelper placeholderHelper = Singleton.get(PlaceholderHelper.class);
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
processPropertyValues(registry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
public static Multimap<String, SpringValueDefinition> getBeanName2SpringValueDefinitions() {
return beanName2SpringValueDefinitions;
}
private void processPropertyValues(BeanDefinitionRegistry beanRegistry) {
// 若已经初始化,直接返回
if (!initialized.compareAndSet(false, true)) {
// already initialized
return;
}
// 循环 BeanDefinition 集合
String[] beanNames = beanRegistry.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName);
// 循环 BeanDefinition 的 PropertyValue 数组
MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
List<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList();
for (PropertyValue propertyValue : propertyValues) {
// 获得 `value` 属性。
Object value = propertyValue.getValue();
// 忽略非 Spring PlaceHolder 的 `value` 属性。
if (!(value instanceof TypedStringValue)) {
continue;
}
// 获得 `placeholder` 属性。
String placeholder = ((TypedStringValue) value).getValue();
// 提取 `keys` 属性们。
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
if (keys.isEmpty()) {
continue;
}
// 循环 `keys` ,创建对应的 SpringValueDefinition 对象,并添加到 `beanName2SpringValueDefinitions` 中。
for (String key : keys) {
beanName2SpringValueDefinitions.put(beanName,
new SpringValueDefinition(key, placeholder, propertyValue.getName()));
}
}
}
}
}

View File

@ -1,31 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.property;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
/**
* {@link SpringValue}
*/
public class SpringValueRegistry {
/**
* SpringValue
*
* KEY KEY Config KEY
* VALUESpringValue
*/
private final Multimap<String, SpringValue> registry = LinkedListMultimap.create();
// 注册
public void register(String key, SpringValue springValue) {
registry.put(key, springValue);
}
// 获得
public Collection<SpringValue> get(String key) {
return registry.get(key);
}
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.dashboard.framework.apollox.spring.util;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import java.util.Objects;
/**
* Bean Registration
*
* @author Jason Song(song_s@ctrip.com)
*/
public class BeanRegistrationUtil {
// 注册 `beanClass` 到 BeanDefinitionRegistry 中,当且仅当 `beanName` 和 `beanClass` 都不存在对应的 BeanDefinition 时
public static boolean registerBeanDefinitionIfNotExists(BeanDefinitionRegistry registry, String beanName, Class<?> beanClass) {
// 不存在 `beanName` 对应的 BeanDefinition
if (registry.containsBeanDefinition(beanName)) {
return false;
}
// 不存在 `beanClass` 对应的 BeanDefinition
String[] candidates = registry.getBeanDefinitionNames();
for (String candidate : candidates) {
BeanDefinition beanDefinition = registry.getBeanDefinition(candidate);
if (Objects.equals(beanDefinition.getBeanClassName(), beanClass.getName())) {
return false;
}
}
// 注册 `beanClass` 到 BeanDefinitionRegistry 中
BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(beanClass).getBeanDefinition();
registry.registerBeanDefinition(beanName, annotationProcessor);
return true;
}
}

View File

@ -1,14 +0,0 @@
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

@ -1,4 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
cn.iocoder.dashboard.framework.apollox.spring.boot.ApolloApplicationContextInitializer

View File

@ -50,10 +50,9 @@ yudao:
# Apollo 配置中心
apollo:
bootstrap:
enabled: true
enabled: true # 设置 Apollo 在启动阶段生效
eagerLoad:
enabled: true
autoUpdateInjectedSpringProperties: true
enabled: true # 设置 Apollo 在日志初始化前生效,可以实现日志的动态级别配置
# MyBatis Plus 的配置项
mybatis-plus: