动态绑定配置

article/2025/9/22 16:37:44

# 一、如何实现动态配置

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

# 二、实现原理

# 2.1 @ConfigurationProperties

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

  1. 实现BeanPostProcessor处理器,初始化时候判断是否被@ConfigurationProperties修饰,如果是就保存到ConfigurationPropertiesBeans#beans属性中
public Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {// 1. 如果已经被RefreshScope修饰了,也会自动更新就不用在处理了。 	if (isRefreshScoped(beanName)) {return bean;}ConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), ConfigurationProperties.class);if (annotation != null) {this.beans.put(beanName, bean);}else if (this.metaData != null) {annotation = this.metaData.findFactoryAnnotation(beanName,ConfigurationProperties.class);if (annotation != null) {this.beans.put(beanName, bean);}}return bean;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  1. ConfigurationPropertiesRebinder 实现 EnvironmentChangeEvent 变更事件, 当收到EnvironmentChangeEvent事件 会重新触发绑定事件。需要绑定的bean就从ConfigurationPropertiesBeans#beans属性中获取。

具体的实现类 ConfigurationPropertiesRebinder

  1. 先调用销毁方法
  2. 然后重新初始化
// 接受事件public void onApplicationEvent(EnvironmentChangeEvent event) {if (this.applicationContext.equals(event.getSource())// Backwards compatible|| event.getKeys().equals(event.getSource())) {rebind();}}// 重新绑定public boolean rebind(String name) {if (!this.beans.getBeanNames().contains(name)) {return false;}if (this.applicationContext != null) {try {Object bean = this.applicationContext.getBean(name);if (AopUtils.isAopProxy(bean)) {bean = ProxyUtils.getTargetObject(bean);}if (bean != null) {this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);return true;}}catch (RuntimeException e) {this.errors.put(name, e);throw e;}catch (Exception e) {this.errors.put(name, e);throw new IllegalStateException("Cannot rebind to " + name, e);}}return false;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

# 2.2 @RefreshScope

@RefreshScope 的原理相对流程较长,首先他需要你将类用 @RefreshScope来修饰。

  1. 首先明确那些是被修饰的AnnotatedBeanDefinitionReader#registerBean
<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}abd.setInstanceSupplier(instanceSupplier);ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);abd.setScope(scopeMetadata.getScopeName());...BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);// 创建bean描述信息 beanClass = ScopedProxyFactoryBean// ScopedProxyCreator#createScopedProxy->ScopedProxyUtils#createScopedProxydefinitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);}		
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
  1. 被Scope修饰的beanClass都是ScopedProxyFactoryBean
    • GenericScope 实现BeanFactoryPostProcessor 会提前将RefreshScope注册到BeanFactory中
    • beanFactory.registerScope(this.name, this)
    • 当执行完上面 AbstractBeanFactory#scopes属性中就有值了。对于RefreshScope name = refresh
public class GenericScope implements Scope, BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor, DisposableBean {}
public class RefreshScope extends GenericScope implements ApplicationContextAware,ApplicationListener<ContextRefreshedEvent>, Ordered {	
}		
1 2 3 4 5 6 7
  1. 当getBean时候,对于域对象会有特殊的处理逻辑,会调用 Scope#get(String name, ObjectFactory<?> objectFactory)
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {...// 创建单例逻辑if (mbd.isSingleton()) {...bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}// 创建原型逻辑else if (mbd.isPrototype()) {...bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}else {// 创建域对象// refreshString scopeName = mbd.getScope();// RefreshScopefinal Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {Object scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}}}}return (T) bean;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
public interface Scope {Object get(String name, ObjectFactory<?> objectFactory); 
}
public class GenericScope implements Scope, BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor, DisposableBean {}
public class RefreshScope extends GenericScope implements ApplicationContextAware,ApplicationListener<ContextRefreshedEvent>, Ordered {}		
1 2 3 4 5 6 7
  1. RefreshEventListener 接受事件,触发刷新操作
public class RefreshEventListener implements SmartApplicationListener {private ContextRefresher refresh;@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationReadyEvent) {handle((ApplicationReadyEvent) event);}else if (event instanceof RefreshEvent) {handle((RefreshEvent) event);}}public void handle(ApplicationReadyEvent event) {this.ready.compareAndSet(false, true);}public void handle(RefreshEvent event) {if (this.ready.get()) { // don't handle events before app is readylog.debug("Event received " + event.getEventDesc());Set<String> keys = this.refresh.refresh();log.info("Refresh keys changed: " + keys);}}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
  1. ContextRefresher#refresh
    1. refreshEnvironment刷新环境
    2. 调用RefreshScope#refreshAll
public class ContextRefresher {public synchronized Set<String> refresh() {Set<String> keys = refreshEnvironment();this.scope.refreshAll();return keys;}public synchronized Set<String> refreshEnvironment() {Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());addConfigFilesToEnvironment();Set<String> keys = changes(before,extract(this.context.getEnvironment().getPropertySources())).keySet();this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));return keys;}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  1. RefreshScope#refreshAll 会将容器中的bean给销毁。 而ScopedProxyFactoryBean中getObject是一个代理对象。带代理类每次都从容器中获取。而容器前面已经将被RefreshScope修饰的类给销毁了 测试拿到的对象就是重新从容器中生成的。
public class ScopedProxyFactoryBean extends ProxyConfigimplements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {private Object proxy;	private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();@Overridepublic void setBeanFactory(BeanFactory beanFactory) {...ProxyFactory pf = new ProxyFactory();pf.copyFrom(this);pf.setTargetSource(this.scopedTargetSource);this.proxy = pf.getProxy(cbf.getBeanClassLoader());}
}		public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {@Overridepublic Object getTarget() throws Exception {return getBeanFactory().getBean(getTargetBeanName());}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# 三、其他方案

因为我们项目中用的是阿波罗,那我们只看阿波罗是如何来做的吧。 在阿波罗只用使用@Value就行了

# 3.1 先扫描@Value注解

将被@Value修饰的Bean和配置key先生成一个SpringValue对象然后注册到SpringValueRegistry

public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {protected void processField(Object bean, String beanName, Field field) {// register @Value on fieldValue value = field.getAnnotation(Value.class);if (value == null) {return;}Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());if (keys.isEmpty()) {return;}for (String key : keys) {SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);springValueRegistry.register(beanFactory, key, springValue);logger.debug("Monitoring {}", springValue);}}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# 3.2 找到需要更新的Bean

接受到配置变更事件后,遍历本地变更的配置key,然后将本次key关联需要变更的Bean,从springValueRegistry中找到。

public class AutoUpdateConfigChangeListener implements ConfigChangeListener{@Overridepublic void onChange(ConfigChangeEvent changeEvent) {Set<String> keys = changeEvent.changedKeys();if (CollectionUtils.isEmpty(keys)) {return;}for (String key : keys) {// 1. check whether the changed key is relevantCollection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);if (targetValues == null || targetValues.isEmpty()) {continue;}// 2. update the valuefor (SpringValue val : targetValues) {updateSpringValue(val);}}}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

# 3.3 通过反射的方法注入

public class SpringValue {public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {if (isField()) {injectField(newVal);} else {injectMethod(newVal);}}private void injectField(Object newVal) throws IllegalAccessException {Object bean = beanRef.get();if (bean == null) {return;}boolean accessible = field.isAccessible();field.setAccessible(true);field.set(bean, newVal);field.setAccessible(accessible);}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

非常简单,高效。相比使用@RefreshScope是不是清爽多了呢?


http://chatgpt.dhexx.cn/article/JgPkb14a.shtml

相关文章

Java的静态绑定与动态绑定

我们可以对思考一个问题&#xff1a; JVM是如何知道调用的是哪个类的方法源代码&#xff1f; 这里面到底有什么内幕呢&#xff1f; 这篇文章我们就将揭露JVM方法调用的静态(static binding) 和动态绑定机制(auto binding) 。 理解这两个绑定之前&#xff0c;我们不妨先理解一…

v-bind动态绑定

目录 一、 v-bind的基本使用 1. v-bind的基本使用 2. Class 与 Style 绑定 二、 v-bind动态绑定class 1. v-bind动态绑定class(对象语法) 2. v-bind动态绑定class(数组用法) 三、 v-for和v-bind结合 四、v-bind动态绑定style 1. v-bind动态绑定style(对象语法) 2. v…

Vue中动态绑定

目录 1. v-bind动态绑定class 1.1. v-bind动态绑定class(对象语法) 1.2. v-bind动态绑定class(数组用法) 2. v-for和v-bind结合 3. v-bind动态绑定style 3.1 v-bind动态绑定style(对象语法) 3.2 v-bind动态绑定style(数组语法) 1. v-bind动态绑定class 1.1. v-bind动态绑…

静态绑定与动态绑定

研究问题&#xff1a; https://www.cnblogs.com/ygj0930/p/6554103.html 要点&#xff1a; 一&#xff1a;绑定   把一个方法与其所在的类/对象 关联起来叫做方法的绑定。绑定分为静态绑定&#xff08;前期绑定&#xff09;和动态绑定&#xff08;后期绑定&#xff09;。 …

动态绑定

文章目录 动态绑定发生的条件在构造方法中调用重写的方法(一个坑) 动态绑定 动态绑定也叫运行时绑定&#xff0c;通俗的讲, 在Java中, 调用某 个类的方法,究竟执行了哪段代码(是父类方法的代码还 是子类方法的代码) , 要看究竟这个引用指向的是父类对 象还是子类对象. 这个过程…

动态绑定,多态(带你从本质了解多态)

在上一章节中&#xff0c;我们讲述了虚函数和虚函数表&#xff0c;我们知道了不管在类中有多少个虚函数&#xff0c;都不会使类的大小扩大&#xff0c;在this指针中&#xff0c;只会多出一个虚函数表的地址&#xff0c;是this指针的第一个内容&#xff0c;在虚函数表中&#xf…

数据结构——哈希表(Hash表)、哈希碰撞

1.概述 哈希表&#xff08;也叫散列表&#xff09;&#xff0c;是根据键&#xff08;Key&#xff09;直接访问在内存存储位置的数据结构。就是一种以 键-值(key-value) 存储数据的结构&#xff0c;我们只要输入key&#xff0c;就可查找到其对应的值。 hash函数就是根据key计算…

24-哈希碰撞攻击是什么?

24-哈希碰撞攻击是什么&#xff1f; 最近哈希表碰撞攻击&#xff08;Hashtable collisions as DOS attack&#xff09;的话题不断被提起&#xff0c;各种语言纷纷中招。本文结合PHP内核源码&#xff0c;聊一聊这种攻击的原理及实现。 哈希表碰撞攻击的基本原理 哈希表是一种…

hashcode及哈希碰撞

数据结构中&#xff1a; 用来映射元素关键字(能唯一标识该元素&#xff0c;类似数据库中的主键可以唯一标识一条记录)和元素的内存地址的关系(解决树&#xff0c;线程表等结构中元素和位置无确定关系&#xff0c;查找时需要进行不断比较的问题。顺序查找的比较结果是和不等。树…

hash和hash碰撞以及解决方案

hash&#xff1a; Hash&#xff0c;一般翻译做“散列”&#xff0c;也有直接音译为“哈希”的&#xff0c;就是把任意长度的输入&#xff08;又叫做预映射&#xff0c; pre-image&#xff09;&#xff0c;通过散列算法&#xff0c;变换成固定长度的输出&#xff0c;该输出就是…

哈希碰撞攻击与防范机制

1.引子 哈希表的原理是用数组来保存键值对&#xff0c;键值对存放的位置&#xff08;下标&#xff09;由键的哈希值决定&#xff0c;键的哈希值可以在参数时间内计算出来&#xff0c;这样哈希表插入、查找和删除的时间复杂度为O(1)&#xff0c;但是这是理想的情况下&#xff0…

HashMap的实现原理及hash冲突(碰撞)解决方法

HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。当程序执行 map.put(String,Obect)方法 时&#xff0c;系统将调用String的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法&#xff0c;都可通过该方法获得它的 hashCode 值。得到这…

解决Hash碰撞冲突方法总结

Hash碰撞冲突 我们知道&#xff0c;对象Hash的前提是实现equals()和hashCode()两个方法&#xff0c;那么HashCode()的作用就是保证对象返回唯一hash值&#xff0c;但当两个对象计算值一样时&#xff0c;这就发生了碰撞冲突。如下将介绍如何处理冲突&#xff0c;当然其前提是一…

哈希表碰撞攻击的基本原理

原文地址&#xff1a;http://blog.jobbole.com/11516/ 来源&#xff1a;张洋 最近哈希表碰撞攻击&#xff08;Hashtable collisions as DOS attack&#xff09;的话题不断被提起&#xff0c;各种语言纷纷中招。本文结合PHP内核源码&#xff0c;聊一聊这种攻击的原理及实现。 …

hash碰撞处理方法

目录 哈希表 哈希冲突 解决碰撞方法 1、开放定址法 a)、线性探测法 a)、二次探测法 c&#xff09;伪随机探测 2、再哈希法 3、拉链法 4、建立公共溢出区 哈希表 是一种实现关联数组抽象数据类型的数据结构&#xff0c;这种结构可以将关键码映射到给定值。 简单来说…

通俗讲解哈希表,哈希碰撞问题!

哈希表是个啥&#xff1f; 小白&#xff1a; 庆哥&#xff0c;什么是哈希表&#xff1f;这个哈希好熟悉&#xff0c;记得好像有HashMap和HashTable之类的吧&#xff0c;这是一样的嘛&#xff1f;&#x1f60a; 庆哥&#xff1a; 这个哈希确实经常见&#x1f602;&#xff0c;足…

哈希碰撞是个什么鬼?

什么是哈希算法&#xff1f; 哈希算法&#xff0c;也叫哈希函数&#xff0c;散列函数&#xff0c;是将任意长度的二进制值映射为较短的固定长度的二进制值&#xff0c;即哈希值。哈希算法是一种只能加密&#xff0c;不能解密的特殊算法。 什么是哈希碰撞&#xff1f; 如果不…

哈希值与哈希碰撞

哈希碰撞 一、什么是哈希&#xff1f; 哈希&#xff08;hash&#xff09;就是讲不同的输入&#xff0c;映射成独一无二、固定长度的值&#xff0c;既哈希值。 我们可以理解为商品的条形码。任何商品都会有一个固定长度而又固定的条码。它的作用就类似于哈希。 哈希值长度可…

【Java】哈希冲突(哈希碰撞)

文章目录 为什么发生哈希冲突&#xff08;哈希碰撞&#xff09;能否完全避免哈希冲突常用处理哈希冲突的方法1.开放地址法1.1线性探测再散列缺点&#xff1a;二次聚集 1.2二次探测再散列1.3伪随机探测再散列 2.再哈希地址法3.链地址法4.建立公共溢出区 为什么发生哈希冲突&…

什么是哈希,哈希表,哈希函数,哈希碰撞?

什么是哈希&#xff1f; 比方我有个原始值&#xff0c;S[“老铁双击666”,‘感谢老铁送的飞机’]&#xff0c; 通过某种算法&#xff08;比如java的hasecode(获得变量的物理地址)&#xff09;得到的666这个就是“哈希码“&#xff08;将字符串转换成尽可能不重复的int类型数字…