SpringBoot动态刷新Environment配置@Value@ConfigurationProperties(5)

本文基于SpringBoot 2.1.6.RELEASE版本

SpringBoot源码解析:Environment加载配置原理(1)
SpringBoot源码解析:Environment获取配置原理(2)
SpringBoot源码解析:@ConfigurationProperties怎么绑定Environment原理探究(3)
SpringBoot源码解析:@Value怎么绑定Environment注入属性值(4)

前文我们介绍了@ConfigurationProperties @Value的基本原理。那我们要动态刷新这些配置(我们从分布式配置中心修改配置,能实时修改我们服务的配置值),又该怎么做呢?

1. 管理界面修改提交配置,服务器监听配置修改事件

  1. SpringCloud config-server修改配置,触发调用config-client,发布RefreshEvent事件,执行RefreshEventListener监听器。

  2. Apollo config-server修改配置,推拉结合方式触发config-client,发布ConfigChangeEvent时间,执行AutoUpdateConfigChangeListener监听器

  3. DisConf config-server修改配置,ZooKeeper通知config-client(Disconf其实并没有和spring environment结合,所以这是disconf-client的一个弊端,我们先不讲)

    综上,其实各种分布式配置中心,都是触发client,发布一个配置修改的事件,通过对应的listener去处理。

2. 加载修改的配置到Environment中

  1. 前文我们介绍过怎么把配置文件中的配置加载到Environment中,就不详细介绍了。
  2. 此处只需要拿到对应修改配置或者对应整个配置文件(通过监听修改,或主动去查询),设置到environment中配置对应的PropertySources中即可。

3. 刷新@ConfigurationProperties配置

​ 刷新@ConfigurationProperties配置相对简单,我们直接看看SpringCloud是怎么做的就好。

  1. ConfigurationPropertiesRebinder监听到加载更新environment完成的事件EnvironmentChangeEvent,就开始重新绑定@ConfigurationProperties配置

  2. ConfigurationPropertiesRebinder(spring-cloud-context.2.1.6.RELEASE)

    //ConfigurationPropertiesBeans是一个BeanPostProcessor,所以bean初始化的时候会把所有@ConfigurationProperties的bean都放进去
    private ConfigurationPropertiesBeans beans;
    
    @ManagedOperation
    public void rebind() {
        this.errors.clear();
        //取出所有含有@ConfigurationProperties的bean,重新绑定配置
        for (String name : this.beans.getBeanNames()) {
            rebind(name);
        }
    }
    
    @ManagedOperation
    public boolean rebind(String name) {
        if (!this.beans.getBeanNames().contains(name)) {
            return false;
        }
        if (this.applicationContext != null) {
            try {
                Object bean = this.applicationContext.getBean(name);
                //判断拿到的bean是否是aop代理,如果是aop代理,拿出原本代理的目标bean
                if (AopUtils.isAopProxy(bean)) {
                    bean = ProxyUtils.getTargetObject(bean);
                }
                //把拿到的bean重新销毁和初始化,就是这么简单(我们知道@ConfigurationProperties的绑定配置类ConfigurationPropertiesBindingPostProcessor是一个BeanPostProcessor,初始化的时候就会调用绑定配置,不清楚的看一下前文解析)
                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;
    }
    

4. 刷新@Value配置

​ 刷新@Value,SpringCloud和Apollo的方式不一样。我们看看Apollo怎么做的

  1. 也是利用一个BeanPostProcessor:ApolloProcessor把bean上面的字段或方法上有@Value注解的注册到SpringValueRegistry

    ApolloProcessor

    public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {
    
      @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName)
          throws BeansException {
        Class clazz = bean.getClass();
          //处理字段和处理方法方式不一样
        for (Field field : findAllField(clazz)) {
          processField(bean, beanName, field);
        }
        for (Method method : findAllMethod(clazz)) {
          processMethod(bean, beanName, method);
        }
        return bean;
      }
        
      //省略代码...
    }
    

    我们看看SpringValueRegistry,注册的结构如下

    public class SpringValueRegistry {
        
      //<BeanFactory,<配置的key值, SpringValue>>
      private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
        
    }
    

    再看看SpringValue结构

    public class SpringValue {
    
        //如果@value在方法上,对应的方法上的参数
      private MethodParameter methodParameter;
        //如果@value是在字段上,对应的字段
      private Field field;
        //bean的引用
      private WeakReference<Object> beanRef;
        //bean名称
      private String beanName;
        //配置的key值
      private String key;
      private String placeholder;
      private Class<?> targetType;
      private Type genericType;
      private boolean isJson;
    }
    
  2. 监听到ConfigChangeEvent事件,查找修改的key是否存在于上面的SpringValueRegistry中,存在,就修改对应的bean的值。

    AutoUpdateConfigChangeListener

    //从SpringValueRegistry中取出了修改key对应的SpringValue对象
    private void updateSpringValue(SpringValue springValue) {
        try {
            //取出修改key对应的environment中的新的value
            Object value = resolvePropertyValue(springValue);
            //更新value
            springValue.update(value);
    
            logger.info("Auto update apollo changed value successfully, new value: {}, {}", value,
                        springValue);
        } catch (Throwable ex) {
            logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
        }
    }
    
  3. 我们看看springValue怎么更新value。

    SpringValue

    //更新的时候,同样区分字段和方法
    public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
        if (isField()) {
            injectField(newVal);
        } else {
            injectMethod(newVal);
        }
    }
    
    //看到如下修改对应bean的值,都是通过反射的方式处理的。
    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);
    }
    
    private void injectMethod(Object newVal)
        throws InvocationTargetException, IllegalAccessException {
        Object bean = beanRef.get();
        if (bean == null) {
            return;
        }
        methodParameter.getMethod().invoke(bean, newVal);
    }
    

    其实更新@ConfigurationProperties和更新@Value不在乎先后顺序。

5. 总结

  1. config-server修改配置,触发config-client(主动,被动)
  2. config-client监听到配置修改,发布一个配置修改事件,由监听器去监听刷新Environment
  3. 刷新Environment之后发布一个Environment更改事件
  4. 监听器监听到以上事件,刷新@ConfigurationProperties(利用BeanPostProcessor初始化Bean),@Value利用反射方式重新绑定新值。
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot的配置文件是基于Spring Framework的Environment和PropertySource抽象构建的。Spring Boot在启动时会创建一个Environment实例,该实例包含了应用程序中所有的属性和配置信息。在应用程序启动时,Spring Boot会将所有的配置文件加载到Environment中,然后将其包装到一个CompositePropertySource中。 当修改了配置文件时,Spring Boot会自动检测到文件的变化,并重新加载Environment中的属性和配置信息。这是通过使用Spring Boot的自动配置机制来实现的。自动配置机制会在应用程序启动时创建一个PropertySourceLoader,并使用它来加载配置文件。当文件发生变化时,自动配置机制会自动重新加载文件,并更新Environment中的属性和配置信息。 在Spring Boot中,可以使用@RefreshScope注解来实现动态刷新配置文件。该注解会使Spring Boot在属性值发生变化时自动更新Bean的值。具体来说,当使用@RefreshScope注解标注一个Bean时,Spring Boot会将该Bean包装在一个代理对象中,代理对象会在每次访问Bean时检查Bean的属性值是否发生了变化,如果发生了变化,则会重新创建Bean实例并返回新的实例。 总之,Spring Boot动态刷新配置文件的原理是通过自动检测配置文件的变化,重新加载属性和配置信息,并使用@RefreshScope注解将Bean包装在代理对象中,在属性值发生变化时重新创建Bean实例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值