前面 分析了apollo配置设置到Spring的environment的过程,此文继续PropertySourcesProcessor.postProcessBeanFactory里面调用的第二个方法initializeAutoUpdatePropertiesFeature(beanFactory),其实也就是配置修改后更新相关处理逻辑。html
在继续分析以前,先来看下配置是怎么自动更新的。java
经过portal页面,修改配置以后,咱们能够经过@ApolloConfigChangeListener来本身监听配置的变化手动更新,也能够经过@Value标记字段,字段值会自动更新。spring
不管是哪一种方式,都离不开event,因此先了解下event相关的对象缓存
// change事件 public class ConfigChangeEvent { private final String m_namespace; private final Map<String, ConfigChange> m_changes; // 省略了所有方法 } // change实体bean public class ConfigChange { private final String namespace; private final String propertyName; private String oldValue; private String newValue; private PropertyChangeType changeType; // 省略了所有方法 } // change类型 public enum PropertyChangeType { ADDED, MODIFIED, DELETED }
直接在属性字段上标记@Value就能够了。app
eg:异步
@Service public class XXXService { @Value("${config.key}") private String XXConfig; .... }
若是修改了config.key配置项,那么这里的XXConfig的值是会自动更新的。ide
@Value方式实现起来很简单,但若是要更灵活点,好比加上一些本身的业务处理,那就须要用到@ApolloConfigChangeListener了。post
@ApolloConfigChangeListener private void onChange(ConfigChangeEvent changeEvent) { logger.info("配置参数发生变化[{}]", JSON.toJSONString(changeEvent)); doSomething(); }
标记有@ApolloConfigChangeListener的这个方法,必须带一个ConfigChangeEvent的入参,经过这个event能够拿到事件的类型、变化的具体字段、变化先后值。this
拿到event以后,咱们能够根据具体的变化作不一样业务处理。spa
以上是更新的使用,下面来深刻研究下源码的实现。
先继续文章开头留下的尾巴 initializeAutoUpdatePropertiesFeature方法。
PropertySourcesProcessor.postProcessBeanFactory –> initializeAutoUpdatePropertiesFeature中。具体逻辑以下:
构造一个 AutoUpdateConfigChangeListener 对象 [implements ConfigChangeListener];
拿到前面处理的全部的ConfigPropertySource组成的list,遍历ConfigPropertySource,设置listener
configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
这里加事件,最终是加在Config上了。
咱们前面使用的@Value注解标记的字段,在字段值发生变化时,就是经过这里加的listener,收到通知的。
@Value是org.springframework.beans.factory.annotation.value,Spring的注解。apollo经过本身的SpringValueProcessor来处理它。来看下完整的流程:
SpringValueProcessor继承了ApolloProcessor,间接实现了BeanPostProcessor
当修改配置发布事件后,AutoUpdateConfigChangeListener就被触发onChange(ConfigChangeEvent changeEvent)事件
经过event拿到全部变动的keys
遍历keys,经过springValueRegistry.get(beanFactory, key) 拿到SpringValue集合对象
这里就是从前面的2.4的map里面获取的
判断配置是否真的发生了变化 shouldTriggerAutoUpdate(changeEvent, key)
遍历SpringValue集合,逐一经过反射改变字段的值
到这里,@Value的更新流程就清楚了。
下面来看看自定义listener是怎么通知更新的。
更多的时候,咱们是经过在方法上标记@ApolloConfigChangeListener来实现本身的监听处理。[例子见1.3代码]
经过@ApolloConfigChangeListener注解添加的监听方法,默认关注的application namespace下的所有配置项。
有关该注解的处理逻辑在 com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor 中,咱们重点关注以下代码段 processMethod :
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); ReflectionUtils.makeAccessible(method); String[] namespaces = annotation.value(); String[] annotatedInterestedKeys = annotation.interestedKeys(); Set<String> interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null; // 建立listener ConfigChangeListener configChangeListener = new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { ReflectionUtils.invokeMethod(method, bean, changeEvent); } }; // 给config设置listener for (String namespace : namespaces) { Config config = ConfigService.getConfig(namespace); if (interestedKeys == null) { config.addChangeListener(configChangeListener); } else { config.addChangeListener(configChangeListener, interestedKeys); } } }
通过这段代码处理,若是有change事件,咱们经过@ApolloConfigChangeListener自定义的listener就会收到消息了。
前面了解完了监听,下面来看下事件的发布。
后台portal页面修改发布以后,client端怎么接收到事件呢?其实在client启动后,就会和服务端建一个长链接。代码见 com.ctrip.framework.apollo.internals.RemoteConfigRepository 。
先来看看RemoteConfigRepository 构造方法
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(); this.trySync(); this.schedulePeriodicRefresh(); this.scheduleLongPollingRefresh(); }
从上面构造方法最后几行能够看出,启动的时候,会先尝试从server端同步,而后会启动2个定时刷新任务,一个是定时刷新,一个是长轮询。不过无论是哪一种方式,最终进入的仍是 trySync() 方法。
以com.ctrip.framework.apollo.internals.RemoteConfigLongPollService为例
RemoteConfigLongPollService
—> startLongPolling()
—> doLongPollingRefresh(appId, cluster, dataCenter),发起http长链接
—> 收到http response(server端发布了新的配置)
—> notify(lastServiceDto, response.getBody());
—> remoteConfigRepository.onLongPollNotified(lastServiceDto, remoteMessages);
—> 异步调用 remoteConfigRepository.trySync();
在trySync方法中,直接调用sync()后就返回,因此须要看sync()方法内部逻辑。在sync()方法内:
先获取本机缓存的当前配置 ApolloConfig previous = m_configCache.get()
获取server端的最新配置 ApolloConfig current = loadApolloConfig()
若是 previous != current ,更新m_configCache
m_configCache.set(current);
this.fireRepositoryChange(m_namespace, this.getConfig());
在fireRepositoryChange里面,遍历当前namespace下的listener,调用RepositoryChangeListener事件
listener.onRepositoryChange(namespace, newProperties)
<—这里事件就传到LocalFileConfigRepository 这一层了
本机该namespace的LocalFileConfigRepository实现了RepositoryChangeListener,因此会受到通知调用
在LocalFileConfigRepository的onRepositoryChange方法中:
比较newProperties.equals(m_fileProperties),相同就直接return,不然继续往下
更新本机缓存文件 updateFileProperties
触发 fireRepositoryChange(namespace, newProperties); <—这里事件就传到Config这一层了
触发事件在 DefaultConfig 中获得响应处理。这里先对发生变化的配置作一些处理,而后发ConfigChangeEvent事件
this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));
在DefaultConfig的this.fireConfigChange里面,就会遍历 listeners,依次调用onChange方法
准确的说,是在父类AbstractConfig中实现的;
这里的listeners就有前面提到的AutoUpdateConfigChangeListener 和 ApolloAnnotationProcessor 中定义的ConfigChangeListener
至此,事件的发布监听就造成闭环了,这里fireConfigChange(ConfigChangeEvent)后,@Value标记的字段、@ApolloConfigChangeListener都会被触发更新。