项目概况

动态绑定配置

2026-01-295 min read后端技术
Spring

一、如何实现动态配置

在Spring体系下,如果实现了ConfigurationProperties则会自动刷新。而如果只使用@Value的方法,要加上 @RefreshScope 才能实现。 本篇文章我们来分别研究下他们的原理。然后在来看看其他的方案是如何做的吧。

二、实现原理

2.1 @ConfigurationProperties

所有被@ConfigurationProperties修饰的类都会被ConfigurationPropertiesBeans处理

  1. 实现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
  1. ConfigurationPropertiesRebinder 实现 EnvironmentChangeEvent 变更事件, 当收到EnvironmentChangeEvent事件 会重新触发绑定事件。需要绑定的bean就从ConfigurationPropertiesBeans#beans属性中获取。

具体的实现类 ConfigurationPropertiesRebinder

  1. 先调用销毁方法
  2. 然后重新初始化
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来修饰。

  1. 首先明确那些是被修饰的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}
  1. 被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}
  1. 当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 {}
  1. 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}
  1. ContextRefresher#refresh
    1. refreshEnvironment刷新环境
    2. 调用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}
  1. 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是不是清爽多了呢?