......
public class ApolloApplicationContextInitializer implements
public class ApolloApplicationContextInitializer implements
// 这个接口允许在ConfigurableApplicationContext(如AnnotationConfigApplicationContext或XmlWebApplicationContext等)刷新(refresh)之前对其进行初始化操作。
// 可以在应用上下文启动但还未加载 bean 定义之前,对上下文进行自定义的设置和初始化操作
ApplicationContextInitializer<ConfigurableApplicationContext>,
// 这个接口允许在 Spring 应用的Environment准备好之后对其进行进一步的处理和定制。
// 可以在 Spring 应用上下文加载环境变量、系统属性和配置文件等之后,对Environment对象进行额外的配置和调整。
EnvironmentPostProcessor,
Ordered
{
// 默认顺序
public static final int DEFAULT_ORDER = 0;
// 日志
private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class);
// namespace分割
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
// apollo系统特性
private static final String[] APOLLO_SYSTEM_PROPERTIES = new String[]{
"app.id",
"apollo.cluster",
"apollo.cacheDir",
"apollo.accesskey.secret",
"apollo.meta",
"apollo.property.order.enable"
};
// 拿到一个线程安全的配置工厂对象
private final ConfigPropertySourceFactory configPropertySourceFactory = (ConfigPropertySourceFactory)SpringInjector.getInstance(ConfigPropertySourceFactory.class);
private int order = 0;
public ApolloApplicationContextInitializer() {
}
// ApolloApplicationContextInitializer在initialize方法中对应用上下文进行初始化操作
@Override
public void initialize( /**当前应用上下文**/ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
if (!(Boolean)environment.getProperty("apollo.bootstrap.enabled", Boolean.class, false)) {
logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, "apollo.bootstrap.enabled");
} else {
logger.debug("Apollo bootstrap config is enabled for context {}", context);
this.initialize(environment);
}
}
// 当前累自定义的方法:initialize
protected void initialize(/**当前应用的配置环境**/ConfigurableEnvironment environment) {
// 检测是否已经有了组合属性对象(经过处理话后,会在环境对象中创建一个,没有则说明尚未完成初始化)
if (!environment.getPropertySources().contains("ApolloBootstrapPropertySources")) {
// 获取配置的apollo的namespace,默认为:application
String namespaces = environment.getProperty("apollo.bootstrap.namespaces", "application");
logger.debug("Apollo bootstrap namespaces: {}", namespaces);
// 按照英文:“,”进行分割,拿到命名空间集合
List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
// 创建一个组合属性对象
CompositePropertySource composite = new CompositePropertySource("ApolloBootstrapPropertySources");
// 拿到迭代器:准备迭代处理
Iterator i$ = namespaceList.iterator();
while(i$.hasNext()) {
// 命名空间名称
String namespace = (String)i$.next();
// 拿到具体的配置(重点逻辑:这里面有从apollo-server获取陪配置数据的逻辑)
Config config = ConfigService.getConfig(namespace);
// 往组合属性对象中丢数据
composite.addPropertySource(this.configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
// 最终数据打包丢到环境对象中
environment.getPropertySources().addFirst(composite);
}
}
void initializeSystemProperty(/**当前应用的配置环境**/ConfigurableEnvironment environment) {
String[] arr$ = APOLLO_SYSTEM_PROPERTIES;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
String propertyName = arr$[i$];
this.fillSystemPropertyFromEnvironment(environment, propertyName);
}
}
private void fillSystemPropertyFromEnvironment(/**当前应用的配置环境**/ConfigurableEnvironment environment,
String propertyName) {
if (System.getProperty(propertyName) == null) {
String propertyValue = environment.getProperty(propertyName);
if (!Strings.isNullOrEmpty(propertyValue)) {
System.setProperty(propertyName, propertyValue);
}
}
}
// 在postProcessEnvironment方法中对环境进行处理
// 执行时间节点:高于initialize( /**当前应用上下文**/ConfigurableApplicationContext context)
@Override
public void postProcessEnvironment(/**当前应用的配置环境**/ConfigurableEnvironment configurableEnvironment,
SpringApplication springApplication) {
// 初始化系统参数
this.initializeSystemProperty(configurableEnvironment);
// # 表示启用急切加载。
// 急切加载意味着在应用启动时,Apollo 客户端会尽快加载配置,而不是在首次访问配置时才加载。这可以确保应用在启动过程中尽早获取到所需的配置信息,减少潜在的延迟和不确定性。
Boolean eagerLoadEnabled = (Boolean)configurableEnvironment.getProperty("apollo.bootstrap.eagerLoad.enabled", Boolean.class, false);
if (eagerLoadEnabled) {
// # 启用 Apollo 的引导程序,这是使 Apollo 客户端在应用启动时生效的关键设置。
Boolean bootstrapEnabled = (Boolean)configurableEnvironment.getProperty("apollo.bootstrap.enabled", Boolean.class, false);
if (bootstrapEnabled) {
// 只有配置了对应的参数,才会执行初始化逻辑
this.initialize(configurableEnvironment);
}
}
}
// 通过getOrder方法返回一个执行顺序值
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
触发顺序:
EnvironmentPostProcessor
- postProcessEnvironment(**) [ApolloApplicationContextInitializer.java]
- this.initialize(configurableEnvironment) [ApolloApplicationContextInitializer.java]
- Config config = ConfigService.getConfig(namespace) [ApolloApplicationContextInitializer.java]
- Config getConfig(String namespace) [ConfigService.java]
- getConfig(String namespace) [DefaultConfigManager.java]
-
- factory.create(namespace)
- Config create(String namespace) [DefaultConfigFactory.java]
-
- new DefaultConfig(namespace, this.createLocalConfigRepository(namespace)) :
-
-
- this.createLocalConfigRepository(namespace) :
-
-
-
-
- new LocalFileConfigRepository(*,this.createRemoteConfigRepository(namespace));
-
-
-
-
-
-
- createRemoteConfigRepository(String namespace)
-
-
-
-
-
-
-
-
- 创建RemoteConfigRepository对象,启动各种触发机制
-
-
-
-
-
-
-
-
-
-
- RemoteConfigRepository(String namespace) [RemoteConfigRepository.java]
-
-
-
-
-
-
-
-
-
-
-
-
- this.trySync();
- this.scheduleLongPollingRefresh();
-
-
-
-
-
-
-
- 最后DefaultConfig 类型包装一下
- 数据刷新到环境中
-
- composite.addPropertySource(this.configPropertySourceFactory.getConfigPropertySource(namespace, config));
environment.getPropertySources().addFirst(composite);
public RemoteConfigRepository(String namespace) {
// 命名空间
this.m_namespace = namespace;
//
this.m_configCache = new AtomicReference();
//
this.m_configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
this.m_httpUtil = (HttpUtil)ApolloInjector.getInstance(HttpUtil.class);
this.m_serviceLocator = (ConfigServiceLocator)ApolloInjector.getInstance(ConfigServiceLocator.class);
// 获取远程配置数据:LongPoll实时通讯交互服务,apollo动态刷新就靠它了~(定义一个服务,后面有逻辑触发)
this.remoteConfigLongPollService = (RemoteConfigLongPollService)ApolloInjector.getInstance(RemoteConfigLongPollService.class);
this.m_longPollServiceDto = new AtomicReference();
this.m_remoteMessages = new AtomicReference();
// 限流
this.m_loadConfigRateLimiter = RateLimiter.create((double)this.m_configUtil.getLoadConfigQPS());
this.m_configNeedForceRefresh = new AtomicBoolean(true);
this.m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(this.m_configUtil.getOnErrorRetryInterval(), this.m_configUtil.getOnErrorRetryInterval() * 8L);
this.gson = new Gson();
// 触发一次同步操作:拉取一次数据(apollo客户端首次拉取配置数据)
this.trySync();
// 刷新任务:主动拉取数据
this.schedulePeriodicRefresh();
// 实时刷新任务:启动longpolling机制
this.scheduleLongPollingRefresh();
}
private void schedulePeriodicRefresh() {
logger.debug("Schedule periodic refresh with interval: {} {}", this.m_configUtil.getRefreshInterval(), this.m_configUtil.getRefreshIntervalTimeUnit());
m_executorService.scheduleAtFixedRate(
// 定时任务逻辑
new Runnable() {
public void run() {
Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", RemoteConfigRepository.this.m_namespace));
RemoteConfigRepository.logger.debug("refresh config for namespace: {}", RemoteConfigRepository.this.m_namespace);
// 主动获取配置数据
// 使用抽象父类中的trySync(),触发当前类的:this.sync(); (后文会详细说明这块)
RemoteConfigRepository.this.trySync();
Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
}
},
(long)this.m_configUtil.getRefreshInterval(), // 查看配置为5s,可以通过:apollo.refreshInterval配置调整,首次延迟时间
(long)this.m_configUtil.getRefreshInterval(), // 同上 ,以后执行间隔时间频率
this.m_configUtil.getRefreshIntervalTimeUnit() // 单位:分钟,固定 TimeUnit.MINUTES;
);
}
private void scheduleLongPollingRefresh() {
// 触发LongPolling实时通讯机制
this.remoteConfigLongPollService.submit(this.m_namespace, this);
}
public class RemoteConfigLongPollService {
// 日志
private static final Logger logger = LoggerFactory.getLogger(RemoteConfigLongPollService.class);
private static final Joiner STRING_JOINER = Joiner.on("+");
private static final MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");
private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();
private static final long INIT_NOTIFICATION_ID = -1L;
// 读超时
private static final int LONG_POLLING_READ_TIMEOUT = 90000;
// 一个单线程的执行器
private final ExecutorService m_longPollingService = Executors.newSingleThreadExecutor(ApolloThreadFactory.create("RemoteConfigLongPollService", true));
private final AtomicBoolean m_longPollingStopped = new AtomicBoolean(false);
// 策略
private SchedulePolicy m_longPollFailSchedulePolicyInSecond = new ExponentialSchedulePolicy(1L, 120L);
// 限流
private RateLimiter m_longPollRateLimiter;
private final AtomicBoolean m_longPollStarted = new AtomicBoolean(false);
// 从apollo服务端拿到的不同namespace的结果数据集合:线程安全
private final Multimap<String, RemoteConfigRepository> m_longPollNamespaces = Multimaps.synchronizedSetMultimap(HashMultimap.create());
// 通知信息:<{namespaceName},{notificationId}>
private final ConcurrentMap<String, Long> m_notifications = Maps.newConcurrentMap();
// 通知内容
private final Map<String, ApolloNotificationMessages> m_remoteNotificationMessages = Maps.newConcurrentMap();
// apollo服务端请求数据应答类型
private Type m_responseType = (new TypeToken<List<ApolloConfigNotification>>() {}).getType();
private Gson gson = new Gson();
//
private ConfigUtil m_configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
private HttpUtil m_httpUtil = (HttpUtil)ApolloInjector.getInstance(HttpUtil.class);
// 注册中心服务:获取可用的apollo服务侧的机器信息,用于http通信时使用
// ConfigServiceLocator内置机制:
// (1)schedulePeriodicRefresh():每隔5min刷新一次,注册中心服务列表的数据,可以通过:apollo.refreshInterval配置调整时间间隔和频率
private ConfigServiceLocator m_serviceLocator = (ConfigServiceLocator)ApolloInjector.getInstance(ConfigServiceLocator.class);
// 构造器
public RemoteConfigLongPollService() {
// 搞一个限流器
// 默认QPS=2
this.m_longPollRateLimiter = RateLimiter.create((double)this.m_configUtil.getLongPollQPS());
}
// 提交的任务从这里开始
public boolean submit(String namespace, // 命名空间
RemoteConfigRepository remoteConfigRepository // 上文传递过来的操作对象:this
) {
// 保存当前要查询的namespace数据,主要是有上下文数据,为了数据安全一致,不会出现错乱问题
boolean added = this.m_longPollNamespaces.put(namespace, remoteConfigRepository);
this.m_notifications.putIfAbsent(namespace, -1L);
// 如果尚未启动任务,则立即启动
if (!this.m_longPollStarted.get()) {
// 任务启动
this.startLongPolling();
}
return added;
}
// 任务核心逻辑
private void startLongPolling() {
// 原子操作:更新任务状态:true
if (this.m_longPollStarted.compareAndSet(false, true)) {
try {
// 从配置对象中拿到相关apollo配置参数,可以从ConfigUtil.java找到控制参数
final String appId = this.m_configUtil.getAppId();
final String cluster = this.m_configUtil.getCluster();
final String dataCenter = this.m_configUtil.getDataCenter();
final String secret = this.m_configUtil.getAccessKeySecret();
// 参数:apollo.longPollingInitialDelayInMills,默认2000L
final long longPollingInitialDelayInMills = this.m_configUtil.getLongPollingInitialDelayInMills();
// 一个单线程的执行器
this.m_longPollingService.submit(new Runnable() {
public void run() {
if (longPollingInitialDelayInMills > 0L) {
try {
RemoteConfigLongPollService.logger.debug("Long polling will start in {} ms.", longPollingInitialDelayInMills);
// sleep休眠的方式,控制刷新频率
TimeUnit.MILLISECONDS.sleep(longPollingInitialDelayInMills);
} catch (InterruptedException var2) {
}
}
// 开始从apollo获取配置数据
RemoteConfigLongPollService.this.doLongPollingRefresh(appId, cluster, dataCenter, secret);
}
});
} catch (Throwable var7) {
this.m_longPollStarted.set(false);
ApolloConfigException exception = new ApolloConfigException("Schedule long polling refresh failed", var7);
Tracer.logError(exception);
logger.warn(ExceptionUtil.getDetailMessage(exception));
}
}
}
// 任务关停
void stopLongPollingRefresh() {
this.m_longPollingStopped.compareAndSet(false, true);
}
// 从apollo获取配置数据
private void doLongPollingRefresh(String appId, String cluster, String dataCenter, String secret) {
Random random = new Random();
// 通信目标服务信息
ServiceDTO lastServiceDto = null;
// 非停止状态,当前线程正常运行的情况下
while(!this.m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
// 限流机制(默认2次)
if (!this.m_longPollRateLimiter.tryAcquire(5L, TimeUnit.SECONDS)) {
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException var21) {
}
}
// 事务控制(Tracer追踪工具)
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "pollNotification");
String url = null;
try {
if (lastServiceDto == null) {
// 从注册中心拿到机器注册列表
List<ServiceDTO> configServices = this.getConfigServices();
// 随便选举一个
lastServiceDto = (ServiceDTO)configServices.get(random.nextInt(configServices.size()));
}
// 组装目标服务的url
url = this.assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, dataCenter, this.m_notifications);
logger.debug("Long polling from {}", url);
// 组装request
HttpRequest request = new HttpRequest(url);
request.setReadTimeout(90000);
// 密钥数据配置
if (!StringUtils.isBlank(secret)) {
Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
request.setHeaders(headers);
}
transaction.addData("Url", url);
// 主动发起GET请求,获取通知url:notifications/v2/***
HttpResponse<List<ApolloConfigNotification>> response = this.m_httpUtil.doGet(request, this.m_responseType);
logger.debug("Long polling response: {}, url: {}", response.getStatusCode(), url);
// 结果数据检测&处理
// 成功
if (response.getStatusCode() == 200 && response.getBody() != null) {
// 收集通知目标数据·1
this.updateNotifications((List)response.getBody());
// 收集通知目标数据·2
this.updateRemoteNotifications((List)response.getBody());
transaction.addData("Result", ((List)response.getBody()).toString());
// 收集完成后,开始通知操作
this.notify(lastServiceDto, (List)response.getBody());
}
// 异常
if (response.getStatusCode() == 304 && random.nextBoolean()) {
lastServiceDto = null;
}
// 一次longpoll完成
this.m_longPollFailSchedulePolicyInSecond.success();
transaction.addData("StatusCode", response.getStatusCode());
transaction.setStatus("0");
} catch (Throwable var19) {
lastServiceDto = null;
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(var19));
transaction.setStatus(var19);
// 失败时,有自己的休眠时间
long sleepTimeInSecond = this.m_longPollFailSchedulePolicyInSecond.fail();
logger.warn("Long polling failed, will retry in {} seconds. appId: {}, cluster: {}, namespaces: {}, long polling url: {}, reason: {}", new Object[]{sleepTimeInSecond, appId, cluster, this.assembleNamespaces(), url, ExceptionUtil.getDetailMessage(var19)});
try {
// 休息一会
TimeUnit.SECONDS.sleep(sleepTimeInSecond);
} catch (InterruptedException var18) {
}
} finally {
// 事务完成
transaction.complete();
}
}
}
// 收集完成后,开始通知操作
private void notify(ServiceDTO lastServiceDto, // 注册中心,续接前文中使用的目标机器的信息
List<ApolloConfigNotification> notifications // 消息通知数据
) {
if (notifications != null && !notifications.isEmpty()) {
Iterator i$ = notifications.iterator();
while(i$.hasNext()) {
ApolloConfigNotification notification = (ApolloConfigNotification)i$.next();
String namespaceName = notification.getNamespaceName();
// 拿到需要被通知的ID
List<RemoteConfigRepository> toBeNotified = Lists.newArrayList(this.m_longPollNamespaces.get(namespaceName));
// 拿到需要被通知的Messages
ApolloNotificationMessages originalMessages = (ApolloNotificationMessages)this.m_remoteNotificationMessages.get(namespaceName);
// 克隆一份
ApolloNotificationMessages remoteMessages = originalMessages == null ? null : originalMessages.clone();
// 追加一种扩展数据:比如:application.properties,有则加入,没有
toBeNotified.addAll(this.m_longPollNamespaces.get(String.format("%s.%s", namespaceName, ConfigFileFormat.Properties.getValue())));
Iterator i$ = toBeNotified.iterator();
// 迭代
while(i$.hasNext()) {
// 拿到当前namespaceName在前文装配好的RemoteConfigRepository数据
RemoteConfigRepository remoteConfigRepository = (RemoteConfigRepository)i$.next();
try {
// 调用前文操作对象,启动通知逻辑(详细后续逻辑移步:RemoteConfigRepository.java文件)
// 通知内容:举例:remoteMessages的格式:123+default+java-project -> {Long@6682} 16
remoteConfigRepository.onLongPollNotified(lastServiceDto, remoteMessages);
} catch (Throwable var12) {
Tracer.logError(var12);
}
}
}
}
}
// 收集通知目标数据·1
private void updateNotifications(List<ApolloConfigNotification> deltaNotifications) {
Iterator i$ = deltaNotifications.iterator();
while(i$.hasNext()) {
ApolloConfigNotification notification = (ApolloConfigNotification)i$.next();
if (!Strings.isNullOrEmpty(notification.getNamespaceName())) {
String namespaceName = notification.getNamespaceName();
if (this.m_notifications.containsKey(namespaceName)) {
this.m_notifications.put(namespaceName, notification.getNotificationId());
}
// 待前缀的
// 比如:java-project.properties 或者 application.properties
String namespaceNameWithPropertiesSuffix = String.format("%s.%s", namespaceName, ConfigFileFormat.Properties.getValue());
if (this.m_notifications.containsKey(namespaceNameWithPropertiesSuffix)) {
this.m_notifications.put(namespaceNameWithPropertiesSuffix, notification.getNotificationId());
}
}
}
}
// 收集通知目标数据·2
private void updateRemoteNotifications(List<ApolloConfigNotification> deltaNotifications) {
Iterator i$ = deltaNotifications.iterator();
while(i$.hasNext()) {
ApolloConfigNotification notification = (ApolloConfigNotification)i$.next();
if (!Strings.isNullOrEmpty(notification.getNamespaceName()) && notification.getMessages() != null && !notification.getMessages().isEmpty()) {
ApolloNotificationMessages localRemoteMessages = (ApolloNotificationMessages)this.m_remoteNotificationMessages.get(notification.getNamespaceName());
if (localRemoteMessages == null) {
localRemoteMessages = new ApolloNotificationMessages();
this.m_remoteNotificationMessages.put(notification.getNamespaceName(), localRemoteMessages);
}
// 收集数据:Messages:123+default+application -> {Long@6671} 19
// 数据装填
localRemoteMessages.mergeFrom(notification.getMessages());
}
}
}
private String assembleNamespaces() {
return STRING_JOINER.join(this.m_longPollNamespaces.keySet());
}
// 组装目标服务的url
String assembleLongPollRefreshUrl(String uri, String appId, String cluster, String dataCenter, Map<String, Long> notificationsMap) {
Map<String, String> queryParams = Maps.newHashMap();
queryParams.put("appId", queryParamEscaper.escape(appId));
queryParams.put("cluster", queryParamEscaper.escape(cluster));
queryParams.put("notifications", queryParamEscaper.escape(this.assembleNotifications(notificationsMap)));
if (!Strings.isNullOrEmpty(dataCenter)) {
queryParams.put("dataCenter", queryParamEscaper.escape(dataCenter));
}
String localIp = this.m_configUtil.getLocalIp();
if (!Strings.isNullOrEmpty(localIp)) {
queryParams.put("ip", queryParamEscaper.escape(localIp));
}
String params = MAP_JOINER.join(queryParams);
if (!uri.endsWith("/")) {
uri = uri + "/";
}
return uri + "notifications/v2?" + params;
}
String assembleNotifications(Map<String, Long> notificationsMap) {
List<ApolloConfigNotification> notifications = Lists.newArrayList();
Iterator i$ = notificationsMap.entrySet().iterator();
while(i$.hasNext()) {
Entry<String, Long> entry = (Entry)i$.next();
ApolloConfigNotification notification = new ApolloConfigNotification((String)entry.getKey(), (Long)entry.getValue());
notifications.add(notification);
}
return this.gson.toJson(notifications);
}
private List<ServiceDTO> getConfigServices() {
List<ServiceDTO> services = this.m_serviceLocator.getConfigServices();
if (services.size() == 0) {
throw new ApolloConfigException("No available config service");
} else {
return services;
}
}
}
......
// longpoll的注册中心选举出的目标服务信息(具有原子性)
private final AtomicReference<ServiceDTO> m_longPollServiceDto;
// 当前namespaceName对应的通知remoteMessages信息(具有原子性)
private final AtomicReference<ApolloNotificationMessages> m_remoteMessages;
// 单线程任务执行器
private static final ScheduledExecutorService m_executorService =
Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("RemoteConfigRepository", true));
......
// 来之RemoteConfigLongPollService.java触发的方法
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, // 注册中心,续接前文中使用的目标机器的信息
// 通知内容:举例:remoteMessages的格式:123+default+java-project -> {Long@6682} 16
ApolloNotificationMessages remoteMessages
) {
// longpoll的注册中心选举出的目标服务信息(具有原子性)
this.m_longPollServiceDto.set(longPollNotifiedServiceDto);
// 当前namespaceName对应的通知remoteMessages信息(具有原子性)
this.m_remoteMessages.set(remoteMessages);
// 单线程任务执行器
m_executorService.submit(new Runnable() {
public void run() {
// 强制刷新标记:true
RemoteConfigRepository.this.m_configNeedForceRefresh.set(true);
// 使用抽象父类中的trySync(),触发当前类的:this.sync(); (后文会详细说明这块)
// 主动触发一次同步操作(和服务启动时,主动触发的方式一致,只不过这里在longpoll机制中添加了触发锚点)
RemoteConfigRepository.this.trySync();
}
});
}
......
续接前文,针对RemoteConfigRepository.java 分析一下:
......
public interface ConfigRepository {
Properties getConfig();
void setUpstreamRepository(ConfigRepository var1);
void addChangeListener(RepositoryChangeListener var1);
void removeChangeListener(RepositoryChangeListener var1);
ConfigSourceType getSourceType();
}
......
public class DefaultPropertiesFactory implements PropertiesFactory {
// 获取配置工具类
private ConfigUtil m_configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
public DefaultPropertiesFactory() {
}
// 根据参数配置:pollo.property.order.enable=false(默认),获取配置实例
public Properties getPropertiesInstance() {
return (Properties)(this.m_configUtil.isPropertiesOrderEnabled() ? new OrderedProperties() : new Properties());
}
}
......
public abstract class AbstractConfigRepository implements ConfigRepository {
// 日志
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class);
// Change监听器
private List<RepositoryChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
// 拿到默认的:DefaultPropertiesFactory.java实例对象
protected PropertiesFactory propertiesFactory = (PropertiesFactory)ApolloInjector.getInstance(PropertiesFactory.class);
public AbstractConfigRepository() {
}
// 就是包装了一层
protected boolean trySync() {
try {
this.sync();
return true;
} catch (Throwable var2) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(var2));
logger.warn("Sync config failed, will retry. Repository {}, reason: {}", this.getClass(), ExceptionUtil.getDetailMessage(var2));
return false;
}
}
// 抽象方法:子类负责实现具体的逻辑
protected abstract void sync();
// 添加监听器
public void addChangeListener(RepositoryChangeListener listener) {
if (!this.m_listeners.contains(listener)) {
this.m_listeners.add(listener);
}
}
// 移除监听器
public void removeChangeListener(RepositoryChangeListener listener) {
this.m_listeners.remove(listener);
}
// 监听器开始生效
protected void fireRepositoryChange(String namespace, Properties newProperties) {
Iterator i$ = this.m_listeners.iterator();
while(i$.hasNext()) {
// 拿到每一个监听器
RepositoryChangeListener listener = (RepositoryChangeListener)i$.next();
try {
// 开始生效了(稍后分析一下具体的生效逻辑:RepositoryChangeListener.java)
listener.onRepositoryChange(namespace, newProperties);
} catch (Throwable var6) {
Tracer.logError(var6);
logger.error("Failed to invoke repository change listener {}", listener.getClass(), var6);
}
}
}
}
......
public class RemoteConfigRepository extends AbstractConfigRepository {
// 日志
private static final Logger logger = LoggerFactory.getLogger(RemoteConfigRepository.class);
private static final Joiner STRING_JOINER = Joiner.on("+");
private static final MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");
private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper();
private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();
// 注册中心服务:获取可用的apollo服务侧的机器信息,用于http通信时使用
// ConfigServiceLocator内置机制:
// (1)schedulePeriodicRefresh():每隔5min刷新一次,注册中心服务列表的数据,可以通过:apollo.refreshInterval配置调整时间间隔和频率
private final ConfigServiceLocator m_serviceLocator;
// http工具
private final HttpUtil m_httpUtil;
// 配置工具类
private final ConfigUtil m_configUtil;
// longpoll 机制服务
private final RemoteConfigLongPollService remoteConfigLongPollService;
// 每次从apollo服务端拿的的配置数据保存在缓存中
private volatile AtomicReference<ApolloConfig> m_configCache;
// 命名空间,核心key
private final String m_namespace;
// 单线程任务执行器
private static final ScheduledExecutorService m_executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("RemoteConfigRepository", true));
// longpoll的注册中心选举出的目标服务信息(具有原子性)
private final AtomicReference<ServiceDTO> m_longPollServiceDto;
// 当前namespaceName对应的通知remoteMessages信息(具有原子性)
private final AtomicReference<ApolloNotificationMessages> m_remoteMessages;
// 限流器
private final RateLimiter m_loadConfigRateLimiter;
// 是否强制刷新
private final AtomicBoolean m_configNeedForceRefresh;
// 如果加载失败时启用的失败策略
private final SchedulePolicy m_loadConfigFailSchedulePolicy;
private final Gson gson;
// 构造注入
public RemoteConfigRepository(String namespace) {
// 命名空间,核心key
this.m_namespace = namespace;
//
this.m_configCache = new AtomicReference();
// 配置工具类
this.m_configUtil = (ConfigUtil)ApolloInjector.getInstance(ConfigUtil.class);
this.m_httpUtil = (HttpUtil)ApolloInjector.getInstance(HttpUtil.class);
// 注册中心服务:获取可用的apollo服务侧的机器信息,用于http通信时使用
// ConfigServiceLocator内置机制:
// (1)schedulePeriodicRefresh():每隔5min刷新一次,注册中心服务列表的数据,可以通过:apollo.refreshInterval配置调整时间间隔和频率
this.m_serviceLocator = (ConfigServiceLocator)ApolloInjector.getInstance(ConfigServiceLocator.class);
// 获取远程配置数据:LongPoll实时通讯交互服务,apollo动态刷新就靠它了~(定义一个服务,后面有逻辑触发)
this.remoteConfigLongPollService = (RemoteConfigLongPollService)ApolloInjector.getInstance(RemoteConfigLongPollService.class);
this.m_longPollServiceDto = new AtomicReference();
this.m_remoteMessages = new AtomicReference();
// 限流:默认QPS=2
this.m_loadConfigRateLimiter = RateLimiter.create((double)this.m_configUtil.getLoadConfigQPS());
this.m_configNeedForceRefresh = new AtomicBoolean(true);
this.m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(this.m_configUtil.getOnErrorRetryInterval(), this.m_configUtil.getOnErrorRetryInterval() * 8L);
this.gson = new Gson();
// 触发一次同步操作:拉取一次数据(apollo客户端首次拉取配置数据)
this.trySync();
// 刷新任务:主动拉取数据
this.schedulePeriodicRefresh();
// 实时刷新任务:启动longpolling机制
this.scheduleLongPollingRefresh();
}
// 获取配置:也会主动触发一次apollo数据拉取
public Properties getConfig() {
if (this.m_configCache.get() == null) {
this.sync();
}
// 数据格式转换
return this.transformApolloConfigToProperties((ApolloConfig)this.m_configCache.get());
}
public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
}
public ConfigSourceType getSourceType() {
return ConfigSourceType.REMOTE;
}
// 刷新任务:主动拉取数据
private void schedulePeriodicRefresh() {
logger.debug("Schedule periodic refresh with interval: {} {}", this.m_configUtil.getRefreshInterval(), this.m_configUtil.getRefreshIntervalTimeUnit());
m_executorService.scheduleAtFixedRate(
// 定时任务逻辑
new Runnable() {
public void run() {
Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", RemoteConfigRepository.this.m_namespace));
RemoteConfigRepository.logger.debug("refresh config for namespace: {}", RemoteConfigRepository.this.m_namespace);
// 主动获取配置数据
// 使用抽象父类中的trySync(),触发当前类的:this.sync(); (后文会详细说明这块)
RemoteConfigRepository.this.trySync();
Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
}
},
(long)this.m_configUtil.getRefreshInterval(), // 查看配置为5s,可以通过:apollo.refreshInterval配置调整,首次延迟时间
(long)this.m_configUtil.getRefreshInterval(), // 同上 ,以后执行间隔时间频率
this.m_configUtil.getRefreshIntervalTimeUnit() // 单位:分钟,固定 TimeUnit.MINUTES;
);
}
// 主动拉取的(核心逻辑)
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");
try {
// 先从缓存中拿数据
ApolloConfig previous = (ApolloConfig)this.m_configCache.get();
// 加载所有数据(核心逻辑)
ApolloConfig current = this.loadApolloConfig();
// 检测数据是否一致
if (previous != current) {
// 不一致,说明远程数据被刷新了
logger.debug("Remote Config refreshed!");
// 更新缓存数据
this.m_configCache.set(current);
// 主动触发变更(监听器去触发:listener.onRepositoryChange(namespace, newProperties);)
this.fireRepositoryChange(this.m_namespace, this.getConfig());
}
if (current != null) {
Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()), current.getReleaseKey());
}
transaction.setStatus("0");
} catch (Throwable var7) {
transaction.setStatus(var7);
throw var7;
} finally {
transaction.complete();
}
}
// 数据格式转换
private Properties transformApolloConfigToProperties(ApolloConfig apolloConfig) {
Properties result = this.propertiesFactory.getPropertiesInstance();
result.putAll(apolloConfig.getConfigurations());
return result;
}
// 加载所有数据(核心逻辑)
private ApolloConfig loadApolloConfig() {
// 限流器
if (!this.m_loadConfigRateLimiter.tryAcquire(5L, TimeUnit.SECONDS)) {
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException var27) {
}
}
// 拿到配置参数
String appId = this.m_configUtil.getAppId();
String cluster = this.m_configUtil.getCluster();
String dataCenter = this.m_configUtil.getDataCenter();
String secret = this.m_configUtil.getAccessKeySecret();
Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, new Object[]{this.m_namespace}));
// 重试次数
int maxRetries = this.m_configNeedForceRefresh.get() ? 2 : 1;
// 失败休眠时间
long onErrorSleepTime = 0L;
Throwable exception = null;
// 拿到注册中心中的目标服务列表
List<ServiceDTO> configServices = this.getConfigServices();
String url = null;
int i = 0;
// 标签通常用于在特定的代码块中进行跳转操作,比如在循环或条件语句中使用break或continue结合标签来控制程序的流程
label194:
while(true) {
if (i < maxRetries) {
// 目标服务列表
List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
// 打乱顺序
Collections.shuffle(randomConfigServices);
if (this.m_longPollServiceDto.get() != null) {
// 如果当前检测到longpoll机制中存在可用目标服务,则添加进来,放在首位,并清空longpoll的服务数据
// 如果此处存在数据,则后面使用的就是这个目标服务的数据
randomConfigServices.add(0, this.m_longPollServiceDto.getAndSet((Object)null));
}
Iterator i$ = randomConfigServices.iterator();
while(true) {
// 劝退机制
if (!i$.hasNext()) {
++i;
continue label194;
}
// 因为被随机打乱了顺序,次数为随机一个目标服务(如果有longpoll的情况下,则延续使用一次)
ServiceDTO configService = (ServiceDTO)i$.next();
if (onErrorSleepTime > 0L) {
logger.warn("Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}", new Object[]{onErrorSleepTime, this.m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, this.m_namespace});
try {
// 休眠
this.m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime);
} catch (InterruptedException var26) {
}
}
// 组装url
url = this.assembleQueryConfigUrl(configService.getHomepageUrl(),
appId,
cluster,
this.m_namespace,
dataCenter,
(ApolloNotificationMessages)this.m_remoteMessages.get(),
(ApolloConfig)this.m_configCache.get()
);
logger.debug("Loading config from {}", url);
// 装配request
HttpRequest request = new HttpRequest(url);
// 装配密钥
if (!StringUtils.isBlank(secret)) {
// 设置头部
Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
request.setHeaders(headers);
}
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig");
transaction.addData("Url", url);
try {
// 主动请求,向后台拉取apollo配置数据
HttpResponse<ApolloConfig> response = this.m_httpUtil.doGet(request, ApolloConfig.class);
// 非强制刷新
this.m_configNeedForceRefresh.set(false);
// 执行成功了
this.m_loadConfigFailSchedulePolicy.success();
transaction.addData("StatusCode", response.getStatusCode());
transaction.setStatus("0");
ApolloConfig result;
// 304异常
if (response.getStatusCode() == 304) {
logger.debug("Config server responds with 304 HTTP status code.");
result = (ApolloConfig)this.m_configCache.get();
return result;
}
// 成功,直接返回
result = (ApolloConfig)response.getBody();
logger.debug("Loaded config for {}: {}", this.m_namespace, result);
ApolloConfig var34 = result;
return var34;
} catch (ApolloConfigStatusCodeException var28) {
ApolloConfigStatusCodeException statusCodeException = var28;
if (var28.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, this.m_namespace);
statusCodeException = new ApolloConfigStatusCodeException(var28.getStatusCode(), message);
}
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException));
transaction.setStatus(statusCodeException);
exception = statusCodeException;
if (var28.getStatusCode() == 404) {
break;
}
} catch (Throwable var29) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(var29));
transaction.setStatus(var29);
exception = var29;
} finally {
transaction.complete();
}
onErrorSleepTime = this.m_configNeedForceRefresh.get() ? this.m_configUtil.getOnErrorRetryInterval() : this.m_loadConfigFailSchedulePolicy.fail();
}
}
String message = String.format("Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s", appId, cluster, this.m_namespace, url);
throw new ApolloConfigException(message, (Throwable)exception);
}
}
// 组装查询url
String assembleQueryConfigUrl(String uri, String appId, String cluster, String namespace, String dataCenter, ApolloNotificationMessages remoteMessages, ApolloConfig previousConfig) {
String path = "configs/%s/%s/%s";
List<String> pathParams = Lists.newArrayList(new String[]{pathEscaper.escape(appId), pathEscaper.escape(cluster), pathEscaper.escape(namespace)});
Map<String, String> queryParams = Maps.newHashMap();
if (previousConfig != null) {
queryParams.put("releaseKey", queryParamEscaper.escape(previousConfig.getReleaseKey()));
}
if (!Strings.isNullOrEmpty(dataCenter)) {
queryParams.put("dataCenter", queryParamEscaper.escape(dataCenter));
}
String localIp = this.m_configUtil.getLocalIp();
if (!Strings.isNullOrEmpty(localIp)) {
queryParams.put("ip", queryParamEscaper.escape(localIp));
}
if (remoteMessages != null) {
queryParams.put("messages", queryParamEscaper.escape(this.gson.toJson(remoteMessages)));
}
String pathExpanded = String.format(path, pathParams.toArray());
if (!queryParams.isEmpty()) {
pathExpanded = pathExpanded + "?" + MAP_JOINER.join(queryParams);
}
if (!uri.endsWith("/")) {
uri = uri + "/";
}
return uri + pathExpanded;
}
private void scheduleLongPollingRefresh() {
// 触发LongPolling实时通讯机制
this.remoteConfigLongPollService.submit(this.m_namespace, this);
}
// 来之RemoteConfigLongPollService.java触发的方法
public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, // 注册中心,续接前文中使用的目标机器的信息
// 通知内容:举例:remoteMessages的格式:123+default+java-project -> {Long@6682} 16
ApolloNotificationMessages remoteMessages
) {
// longpoll的注册中心选举出的目标服务信息(具有原子性)
this.m_longPollServiceDto.set(longPollNotifiedServiceDto);
// 当前namespaceName对应的通知remoteMessages信息(具有原子性)
this.m_remoteMessages.set(remoteMessages);
// 单线程任务执行器
m_executorService.submit(new Runnable() {
public void run() {
// 强制刷新标记:true
RemoteConfigRepository.this.m_configNeedForceRefresh.set(true);
// 使用抽象父类中的trySync(),触发当前类的:this.sync(); (后文会详细说明这块)
// 主动触发一次同步操作(和服务启动时,主动触发的方式一致,只不过这里在longpoll机制中添加了触发锚点)
RemoteConfigRepository.this.trySync();
}
});
}
// 获取注册中心中的目标服务的数据列表
private List<ServiceDTO> getConfigServices() {
// 关键逻辑在:ConfigServiceLocator.java文件中
List<ServiceDTO> services = this.m_serviceLocator.getConfigServices();
if (services.size() == 0) {
throw new ApolloConfigException("No available config service");
} else {
return services;
}
}
}
至此,可以看到,从 apollo 服务端侧拿到了配置数据,接下来要看一下,拿到的数据时如何更新到客户端参数的
还需要续接前文的:ApolloApplicationContextInitializer.java
继续分析!!!
一、 更新过程
- 创建
CompositePropertySource
:
-
- 当 Apollo 客户端从服务端获取到新的配置数据后,会根据这些数据创建一个新的
CompositePropertySource
对象。这个复合属性源包含了从不同命名空间获取的配置信息,每个命名空间对应一个ConfigPropertySource
。
- 当 Apollo 客户端从服务端获取到新的配置数据后,会根据这些数据创建一个新的
- 添加到环境属性源:
-
- 如果环境中已经存在 “ApolloBootstrapPropertySources” 属性源,会确保其优先级最高。然后将新创建的复合属性源添加到环境属性源中,使其在 “ApolloBootstrapPropertySources” 之后。具体实现是通过操作
ConfigurableEnvironment
对象的MutablePropertySources
来完成的。 - 如果没有 “ApolloBootstrapPropertySources”,则将复合属性源添加到环境属性源的首位。
- 如果环境中已经存在 “ApolloBootstrapPropertySources” 属性源,会确保其优先级最高。然后将新创建的复合属性源添加到环境属性源中,使其在 “ApolloBootstrapPropertySources” 之后。具体实现是通过操作
二、生效机制
- Spring 的属性源查找机制:
-
- Spring 在运行时,当需要获取某个属性值时,会从
Environment
对象中查找属性源。Environment
对象维护了一个有序的PropertySources
集合,它会按照特定的顺序遍历这个集合来查找属性值。 - 当
CompositePropertySource
被添加到这个集合中后,Spring 在查找属性时就有机会访问到这个新添加的属性源。
- Spring 在运行时,当需要获取某个属性值时,会从
- 依赖注入与属性解析:
-
- 在 Spring 容器初始化过程中,对于使用了
@Value
注解或者通过Environment
对象获取属性值的 Bean,Spring 会在依赖注入阶段解析这些属性值。 - 当属性源发生变化(即新的
CompositePropertySource
被添加)后,Spring 在解析属性值时会从更新后的属性源中获取最新的值。 - 例如,如果一个 Bean 中有一个属性使用了
@Value("${some.property.key}")
注解,Spring 会在属性注入时从环境属性源中查找对应的值。如果新的配置更新了这个属性的值,那么在下次解析这个注解时,就会获取到新的值,从而实现了配置的动态更新。
- 在 Spring 容器初始化过程中,对于使用了
- 自动更新机制(如果启用):
-
- 如果配置了自动更新属性的功能,当配置发生变化时,注册的监听器(如
AutoUpdateConfigChangeListener
)会被触发。这些监听器会检测到配置的变化,并更新相应的属性源。 - 这样,即使在应用运行过程中配置发生了变化,也能及时更新到环境中,并通过上述机制使应用中的 Bean 能够获取到最新的属性值。
- 如果配置了自动更新属性的功能,当配置发生变化时,注册的监听器(如
综上所述,更新后的CompositePropertySource
通过被添加到 Spring 的环境属性源集合中,利用 Spring 的属性查找和依赖注入机制,以及自动更新机制(如果启用),使得新的配置能够在应用中生效。