增加 apollo 读取本地 db,需要进一步优化~
parent
c4c614592a
commit
17cb37f577
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package cn.iocoder.dashboard.framework.apollox.enums;
|
||||
|
||||
/**
|
||||
* 属性变化类型枚举
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public enum PropertyChangeType {
|
||||
|
||||
ADDED, // 添加
|
||||
MODIFIED, // 修改
|
||||
DELETED // 删除
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
* @ApolloConfig("someNamespace")
|
||||
* private Config config;
|
||||
* </pre>
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Documented
|
||||
public @interface ApolloConfig {
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 们
|
||||
*
|
||||
* 每个 Field、Method、XML PlaceHolder 被处理成一个 SpringValue 对象,添加到 SpringValueRegistry 中。
|
||||
*
|
||||
* 目的还是,为了 PlaceHolder 的自动更新机制。
|
||||
*
|
||||
* @author github.com/zhegexiaohuozi seimimaster@gmail.com
|
||||
* @since 2017/12/20.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor {
|
||||
|
||||
/**
|
||||
* SpringValueDefinition 集合
|
||||
*
|
||||
* KEY:beanName
|
||||
* VALUE:SpringValueDefinition 集合
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 对象
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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; // 最高优先级
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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>
|
||||
* <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
|
||||
* <property name="timeout" value="${timeout:200}"/>
|
||||
* <property name="batch" value="${batch:100}"/>
|
||||
* </bean>
|
||||
* </pre>
|
||||
*/
|
||||
public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
|
||||
|
||||
/**
|
||||
* SpringValueDefinition 集合
|
||||
* <p>
|
||||
* KEY:beanName
|
||||
* VALUE:SpringValueDefinition 集合
|
||||
*/
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
* VALUE:SpringValue 数组
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue