spring cloud 配置属性动态刷新见解

配置属性动态刷新

在微服务环境下,配置中心更改了配置属性后,我们为了能在对应项目上实现动态刷新,常用二种方式
1、使用属性注解

//AbcConfig.java
@Configuration
@EnableConfigurationProperties({AbcProperties.class})
public class AbcConfig {
}

//AbcProperties.java
@ConfigurationProperties(prefix = "pro")
public class AbcProperties {
    public AbcProperties(){
        System.out.println("------------AbcProperties--------------");
    }
    private String abc;


    public String getAbc() {
        return abc;
    }

    public void setAbc(String abc) {
        this.abc = abc;
    }

    @PreDestroy
    public void destroy(){
        System.out.println("------------AbcProperties---destroy-----------");
    }
} 

2、类作用域注解 @RefreshScope

@Component
@RefreshScope 
public class DefProperties{
    public DefProperties(){
        System.out.println("------------DefProperties--------------");
    }
    /** 名称 */
    @Value("${pro.def}")
    private String def;
 
     public String getDef() {
        return def;
    }

    public void setDef(String def) {
        this.def = def;
    }
    
    @PreDestroy
    public void destroy(){
        System.out.println("------------DefProperties---destroy-----------");
    }

本文使用nacos作为配置中心,内容如下

pro: 
 abc: 11
 def: 22

我们先测试 AbcProperties类,更改nacos配置中的abc值。
在nacos的客户端有个线程(ClientWorker.LongPollingRunnable)不断检测本地标识和服务端标识是否一致(md5),不一致就发布刷新事件。

//这里只截部分关键代码,感兴趣的可自己调试下
public class NacosContextRefresher{
	 private void registerNacosListener(final String group, final String dataId) {
		...
		//最终在这里发布刷新事件
	 	NacosContextRefresher.this.applicationContext.publishEvent(new RefreshEvent(this, (Object)null, "Refresh Nacos config"));
		...
	}
}

之后就是spring cloud 的刷新逻辑了

//这个类接收到前面nacos发布出来的 RefreshEvent 刷新事件
public class RefreshEventListener implements SmartApplicationListener {
    private static Log log = LogFactory.getLog(RefreshEventListener.class);
    private ContextRefresher refresh;
    private AtomicBoolean ready = new AtomicBoolean(false);

    public RefreshEventListener(ContextRefresher refresh) {
        this.refresh = refresh;
    }

    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationReadyEvent.class.isAssignableFrom(eventType) || RefreshEvent.class.isAssignableFrom(eventType);
    }

   //接收事件
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationReadyEvent) {
            this.handle((ApplicationReadyEvent)event);
        } else if (event instanceof RefreshEvent) {
           //处理刷新事件
            this.handle((RefreshEvent)event);
        }
    }

    public void handle(ApplicationReadyEvent event) {
        this.ready.compareAndSet(false, true);
    }

    public void handle(RefreshEvent event) {
        if (this.ready.get()) {
            log.debug("Event received " + event.getEventDesc());
            //处理刷新,且返回那些key被刷新过
            Set<String> keys = this.refresh.refresh();
            log.info("Refresh keys changed: " + keys);
        }

    }
}
//上下文刷新类,环境变量的更新 和 RefreshScope注解类的更新,都调用到之类
public class ContextRefresher { 
 
    //前面的this.refresh.refresh(); 将调用到这里
    public synchronized Set<String> refresh() {
        // 环境变量刷新
        Set<String> keys = this.refreshEnvironment();
        
        // RefreshScope 刷新
        this.scope.refreshAll();
        //返回更改过的keys
        return keys;
    }

    public synchronized Set<String> refreshEnvironment() {
        //罗列出当前所有环境变量
        Map<String, Object> before = this.extract(this.context.getEnvironment().getPropertySources());
        
        //加载所有的配置文件包括服务端
        this.addConfigFilesToEnvironment();
        
        //然后把刷新前后的环境变量做比较,看看那些key发生了变化(返回打印用)
        Set<String> keys = this.changes(before, this.extract(this.context.getEnvironment().getPropertySources())).keySet();
        
        //发布个环境变量更新事件,触发环境变量更新逻辑
        //重绑定到对应的类上,比如之前的 AbcProperties  
        this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
        return keys;
    }
 }
public class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
   
   //所有的属性类都刷一遍
    @ManagedOperation
    public void rebind() {
        this.errors.clear();
        Iterator var1 = this.beans.getBeanNames().iterator();

        while(var1.hasNext()) {
            String name = (String)var1.next();
			//根据bean名 刷对应的bean
            this.rebind(name);
        }

    }

    @ManagedOperation
    public boolean rebind(String name) {
        if (!this.beans.getBeanNames().contains(name)) {
            return false;
        } else {
            if (this.applicationContext != null) {
                try {
                    Object bean = this.applicationContext.getBean(name);
                    if (AopUtils.isAopProxy(bean)) {
                        bean = ProxyUtils.getTargetObject(bean);
                    }

                    if (bean != null) {
                        if (this.getNeverRefreshable().contains(bean.getClass().getName())) {
                            return false;
                        }
						//先调用bean的 destroy()方法,
                        this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
                        //重新初始化bean ,(**不是实例化**)
                        this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
                        return true;
                    }
                } catch (RuntimeException var3) {
                    this.errors.put(name, var3);
                    throw var3;
                } catch (Exception var4) {
                    this.errors.put(name, var4);
                    throw new IllegalStateException("Cannot rebind to " + name, var4);
                }
            }

            return false;
        }
    }
 
 // 接收到 前面发布的EnvironmentChangeEvent事件,然后走rebind();
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        if (this.applicationContext.equals(event.getSource()) || event.getKeys().equals(event.getSource())) {
            this.rebind();
        }

    }

执行完后,属性类的字段值就被重新赋值了,类还是原来的类,值不一样了

我们在看看 RefreshScope 是怎么做的
在测试 DefProperties类,更改nacos配置中的def值。
前面都一样,一直到

public class ContextRefresher { 
 
    //前面的this.refresh.refresh(); 将调用到这里
    public synchronized Set<String> refresh() {
        // 环境变量刷新
        Set<String> keys = this.refreshEnvironment();
        
        // RefreshScope 刷新  这次我们看这里
        this.scope.refreshAll();
        //返回更改过的keys
        return keys;
    }
}
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {

  //前面this.scope.refreshAll(); 调用到这里
 public void refreshAll() {
 		//这个方法会清理 作用域为 refresh 的bean工厂和bean缓存 ,且调用bean的 destroy()
 		//对象名为GenericScope.BeanLifecycleWrapper
        super.destroy();
        this.context.publishEvent(new RefreshScopeRefreshedEvent());
    } 

}

到此 刷新事件结束,BeanLifecycleWrapper中缓存的这个bean信息了清理,但ico中它的代理类还在。怎么办?此时,如果那个地方用到了这个bean。spring 通过getBean方法获取到他的代理类,代理类 内部 在去getBean,重新去实例化它 且缓存在GenericScope.BeanLifecycleWrapper 中!
代理类获取bean示例:


class CglibAopProxy implements AopProxy, Serializable {
  public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            
            TargetSource targetSource = this.advised.getTargetSource();

           //获取真实类
            target = targetSource.getTarget();
    }

}
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
    public SimpleBeanTargetSource() {
    }

   //获取到真实bean
    public Object getTarget() throws Exception {
        return this.getBeanFactory().getBean(this.getTargetBeanName());
    }
}

//scope生成bean
在这里插入图片描述
注意下scope 对象,和beanName(这是一个代理bean名称),表达式是一个回调

我们看下他调到了哪里?

//这是 RefreshScope 的父类
class GenericScope{

    //前面调到了这里,放cache缓存中放 bean名和 生产对象
    public Object get(String name, ObjectFactory<?> objectFactory) {
        GenericScope.BeanLifecycleWrapper value = this.cache.put(name, new GenericScope.BeanLifecycleWrapper(name, objectFactory));
        this.locks.putIfAbsent(name, new ReentrantReadWriteLock());

        try {
			// 这里会调到 objectFactory.getObject(); 然后在缓存中就有了真实bean
            return value.getBean();
        } catch (RuntimeException var5) {
            this.errors.put(name, var5);
            throw var5;
        }
    }
}

调用完成后
在这里插入图片描述
到此刷新完成了。这里的bean就是真实bean了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值