动态绑定配置
Spring

一、如何实现动态配置
在Spring体系下,如果实现了ConfigurationProperties则会自动刷新。而如果只使用@Value的方法,要加上 @RefreshScope 才能实现。
本篇文章我们来分别研究下他们的原理。然后在来看看其他的方案是如何做的吧。
二、实现原理
2.1 @ConfigurationProperties
所有被@ConfigurationProperties修饰的类都会被ConfigurationPropertiesBeans处理
- 实现
BeanPostProcessor处理器,初始化时候判断是否被@ConfigurationProperties修饰,如果是就保存到ConfigurationPropertiesBeans#beans属性中
java
1 public Object postProcessBeforeInitialization(Object bean, String beanName)
2 throws BeansException {
3 // 1. 如果已经被RefreshScope修饰了,也会自动更新就不用在处理了。
4 if (isRefreshScoped(beanName)) {
5 return bean;
6 }
7 ConfigurationProperties annotation = AnnotationUtils
8 .findAnnotation(bean.getClass(), ConfigurationProperties.class);
9 if (annotation != null) {
10 this.beans.put(beanName, bean);
11 }
12 else if (this.metaData != null) {
13 annotation = this.metaData.findFactoryAnnotation(beanName,
14 ConfigurationProperties.class);
15 if (annotation != null) {
16 this.beans.put(beanName, bean);
17 }
18 }
19 return bean;
20 }
21- ConfigurationPropertiesRebinder 实现
EnvironmentChangeEvent变更事件, 当收到EnvironmentChangeEvent事件 会重新触发绑定事件。需要绑定的bean就从ConfigurationPropertiesBeans#beans属性中获取。
具体的实现类 ConfigurationPropertiesRebinder
- 先调用销毁方法
- 然后重新初始化
java
1 // 接受事件
2 public void onApplicationEvent(EnvironmentChangeEvent event) {
3 if (this.applicationContext.equals(event.getSource())
4 // Backwards compatible
5 || event.getKeys().equals(event.getSource())) {
6 rebind();
7 }
8 }
9 // 重新绑定
10 public boolean rebind(String name) {
11 if (!this.beans.getBeanNames().contains(name)) {
12 return false;
13 }
14 if (this.applicationContext != null) {
15 try {
16 Object bean = this.applicationContext.getBean(name);
17 if (AopUtils.isAopProxy(bean)) {
18 bean = ProxyUtils.getTargetObject(bean);
19 }
20 if (bean != null) {
21 this.applicationContext.getAutowireCapableBeanFactory()
22 .destroyBean(bean);
23 this.applicationContext.getAutowireCapableBeanFactory()
24 .initializeBean(bean, name);
25 return true;
26 }
27 }
28 catch (RuntimeException e) {
29 this.errors.put(name, e);
30 throw e;
31 }
32 catch (Exception e) {
33 this.errors.put(name, e);
34 throw new IllegalStateException("Cannot rebind to " + name, e);
35 }
36 }
37 return false;
38 }2.2 @RefreshScope
@RefreshScope 的原理相对流程较长,首先他需要你将类用 @RefreshScope来修饰。
- 首先明确那些是被修饰的
AnnotatedBeanDefinitionReader#registerBean。
java
1<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
2 @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
3
4 AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
5 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
6 return;
7 }
8
9 abd.setInstanceSupplier(instanceSupplier);
10 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
11 abd.setScope(scopeMetadata.getScopeName());
12 ...
13 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
14 // 创建bean描述信息 beanClass = ScopedProxyFactoryBean
15 // ScopedProxyCreator#createScopedProxy->ScopedProxyUtils#createScopedProxy
16 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
17 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
18
19}- 被Scope修饰的beanClass都是ScopedProxyFactoryBean
- GenericScope 实现BeanFactoryPostProcessor 会提前将RefreshScope注册到BeanFactory中
- beanFactory.registerScope(this.name, this)
- 当执行完上面 AbstractBeanFactory#scopes属性中就有值了。对于RefreshScope name = refresh
java
1public class GenericScope implements Scope, BeanFactoryPostProcessor,
2 BeanDefinitionRegistryPostProcessor, DisposableBean {
3
4}
5public class RefreshScope extends GenericScope implements ApplicationContextAware,
6 ApplicationListener<ContextRefreshedEvent>, Ordered {
7}- 当getBean时候,对于域对象会有特殊的处理逻辑,会调用
Scope#get(String name, ObjectFactory<?> objectFactory)
java
1 protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
2 @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
3 ...
4 // 创建单例逻辑
5 if (mbd.isSingleton()) {
6 ...
7 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
8 }
9 // 创建原型逻辑
10 else if (mbd.isPrototype()) {
11 ...
12 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
13 }
14 else {
15 // 创建域对象
16 // refresh
17 String scopeName = mbd.getScope();
18 // RefreshScope
19 final Scope scope = this.scopes.get(scopeName);
20 if (scope == null) {
21 throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
22 }
23 try {
24 Object scopedInstance = scope.get(beanName, () -> {
25 beforePrototypeCreation(beanName);
26 try {
27 return createBean(beanName, mbd, args);
28 }
29 finally {
30 afterPrototypeCreation(beanName);
31 }
32 });
33 bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
34 }
35 }
36 }
37 }
38 return (T) bean;
39 }java
1public interface Scope {
2 Object get(String name, ObjectFactory<?> objectFactory);
3}
4public class GenericScope implements Scope, BeanFactoryPostProcessor,
5 BeanDefinitionRegistryPostProcessor, DisposableBean {}
6public class RefreshScope extends GenericScope implements ApplicationContextAware,
7 ApplicationListener<ContextRefreshedEvent>, Ordered {}- RefreshEventListener 接受事件,触发刷新操作
java
1public class RefreshEventListener implements SmartApplicationListener {
2 private ContextRefresher refresh;
3 @Override
4 public void onApplicationEvent(ApplicationEvent event) {
5 if (event instanceof ApplicationReadyEvent) {
6 handle((ApplicationReadyEvent) event);
7 }
8 else if (event instanceof RefreshEvent) {
9 handle((RefreshEvent) event);
10 }
11 }
12
13 public void handle(ApplicationReadyEvent event) {
14 this.ready.compareAndSet(false, true);
15 }
16
17 public void handle(RefreshEvent event) {
18 if (this.ready.get()) { // don't handle events before app is ready
19 log.debug("Event received " + event.getEventDesc());
20 Set<String> keys = this.refresh.refresh();
21 log.info("Refresh keys changed: " + keys);
22 }
23 }
24}- ContextRefresher#refresh
- refreshEnvironment刷新环境
- 调用RefreshScope#refreshAll
java
1public class ContextRefresher {
2 public synchronized Set<String> refresh() {
3 Set<String> keys = refreshEnvironment();
4 this.scope.refreshAll();
5 return keys;
6 }
7
8 public synchronized Set<String> refreshEnvironment() {
9 Map<String, Object> before = extract(
10 this.context.getEnvironment().getPropertySources());
11 addConfigFilesToEnvironment();
12 Set<String> keys = changes(before,
13 extract(this.context.getEnvironment().getPropertySources())).keySet();
14 this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
15 return keys;
16 }
17}- RefreshScope#refreshAll 会将容器中的bean给销毁。 而ScopedProxyFactoryBean中getObject是一个代理对象。带代理类每次都从容器中获取。而容器前面已经将被RefreshScope修饰的类给销毁了 测试拿到的对象就是重新从容器中生成的。
java
1public class ScopedProxyFactoryBean extends ProxyConfig
2 implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {
3 private Object proxy;
4 private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
5 @Override
6 public void setBeanFactory(BeanFactory beanFactory) {
7 ...
8 ProxyFactory pf = new ProxyFactory();
9 pf.copyFrom(this);
10 pf.setTargetSource(this.scopedTargetSource);
11 this.proxy = pf.getProxy(cbf.getBeanClassLoader());
12 }
13}
14
15public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
16 @Override
17 public Object getTarget() throws Exception {
18 return getBeanFactory().getBean(getTargetBeanName());
19 }
20}三、其他方案
因为我们项目中用的是阿波罗,那我们只看阿波罗是如何来做的吧。 在阿波罗只用使用@Value就行了
3.1 先扫描@Value注解
将被@Value修饰的Bean和配置key先生成一个SpringValue对象然后注册到SpringValueRegistry中
java
1public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {
2 protected void processField(Object bean, String beanName, Field field) {
3 // register @Value on field
4 Value value = field.getAnnotation(Value.class);
5 if (value == null) {
6 return;
7 }
8 Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
9
10 if (keys.isEmpty()) {
11 return;
12 }
13
14 for (String key : keys) {
15 SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
16 springValueRegistry.register(beanFactory, key, springValue);
17 logger.debug("Monitoring {}", springValue);
18 }
19 }
20}3.2 找到需要更新的Bean
接受到配置变更事件后,遍历本地变更的配置key,然后将本次key关联需要变更的Bean,从springValueRegistry中找到。
java
1public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
2 @Override
3 public void onChange(ConfigChangeEvent changeEvent) {
4 Set<String> keys = changeEvent.changedKeys();
5 if (CollectionUtils.isEmpty(keys)) {
6 return;
7 }
8 for (String key : keys) {
9 // 1. check whether the changed key is relevant
10 Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
11 if (targetValues == null || targetValues.isEmpty()) {
12 continue;
13 }
14
15 // 2. update the value
16 for (SpringValue val : targetValues) {
17 updateSpringValue(val);
18 }
19 }
20 }
21}3.3 通过反射的方法注入
java
1public class SpringValue {
2 public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
3 if (isField()) {
4 injectField(newVal);
5 } else {
6 injectMethod(newVal);
7 }
8 }
9 private void injectField(Object newVal) throws IllegalAccessException {
10 Object bean = beanRef.get();
11 if (bean == null) {
12 return;
13 }
14 boolean accessible = field.isAccessible();
15 field.setAccessible(true);
16 field.set(bean, newVal);
17 field.setAccessible(accessible);
18 }
19}非常简单,高效。相比使用@RefreshScope是不是清爽多了呢?