开发者

Spring应用中使用acutator/refresh刷新属性不生效的问题分析及解决

开发者 https://www.devze.com 2024-01-11 10:24 出处:网络 作者: 龙鹤鹿
目录问题的引入结论分析应用启动阶段应用运行阶段结论问题的引入 在Spring应用收到/actuator/refresh的POST请求后,标注了@RefreshScope以及@ConfiguratioinProperties的bean会被Spring容器重新加载。这样,如果配置
目录
  • 问题的引入
  • 结论
  • 分析
    • 应用启动阶段
    • 应用运行阶段
  • 结论

    问题的引入

    在Spring应用收到/actuator/refresh的POST请求后,标注了@RefreshScope以及@ConfiguratioinProperties的bean会被Spring容器重新加载。这样,如果配置文件(一般来自于配置中心,或者k8s的ConfigMap)发生了变化,那么这些变化就会因为Bean的重新加载而被应用感知。

    但是,在实际应用中,可能会发现有些标注了@ConfigurationProperties的bean,并没有按照预期被Spring容器加载。本文将讨论导致这种未按预期刷新的一种原因。

    结论

    在进行详细的讨论之前,先把结论写出来。如果大家时间紧张,而且碰巧遇到了这样的问题,可以直接根据结论把问题解决掉。

    确保标注了@ConfigurationProperties注解的bean没有被任何Advisor依赖

    比如:如下的Bean就不会被Spring容器刷新。

    @ConfigurationProperties(prefix="com.dadaer.test")
    public class MyProp {
      //...
    }
    
    // ............
    
    @Component
    public class MyAdvisor extends AbstractPointcutAdvisor {
       
       private final MyProp myProp;
       
       public class MyAdvisor(MyProp myProp) {
         this.myProp = myProp;
       }
    
      //...
    }
    

    这里的MyAdvisor依赖了MyProp,所以在收到/actuator/refresh的请求以后,MyProp的bean不会被重新加载。

    分析

    应用启动阶段

    在Spring应用启动时,在执行到AbstractApplicationContext#refresh方法初始化容器的时候,其中有一个步骤(12大步中的第5步),Spring会向容器中注入所有的BeanPostProcessor,代码如下:

    // Register bean processors that intercept bean creation.
    registerBeanPostProcessors(beanFactory);
    

    而其中有一个BeanPostProcessor叫做:ConfigurationPropertiesBeans,是一个用来注册所有标注了python@ConfigurationProperties注解的后置处理器。

    在初始化BeanPostProcessor的时候,会经历到下面的一段代码:

    @Override
    protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        // TODO: Consider optimization by caching the list of the ASPect names
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        for (Advisor advisor : candi编程客栈dateAdvisors) {
           if (advisor instanceof AspectJPointcutAdvisor pointcutAdvisor &&
                 pointcutAdvisor.getAspectName().equals(beanName)) {
              return true;
           }
        }
        return super.shouldSkip(beanClass, beanName);
    }
    

    这里,会查找所有的Advisor并初始化。这样,如果某个Advisor(比如上述MyAdvisor)依赖了一个@ConfigurationProperties注解的类(比如上述MyProp)。那么此时MyProp就需要在BeanPostProcessor之前初始化完成,即:MyProp先于ConfigurationPropertiesBeans完成初始化。

    应用运行阶段

    在应用运行阶段,当收到/actuandroidator/refresh的POST请求时,会触发RefreshEndpoint

    @Endpoint(id = "refresh")
    public class RefreshEndpoint {
        //...
        @WriteOperation
        public Collection<String> refresh() {
           Set<String> keys = this.contextRefresher.refresh();
           return keys;
        }
    }
    

    然后调用ContextRefresher#refresh方法进入下面的代码:

    public synchronized Set<String> refreshEnvironment() {
        Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
        updateEnvironment();
        Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
        this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
        return keys;
    }
    

    这里,发送了一个EnvironmentChangeEvent事件,这个事件会被ConfigurationPropertiesRebinder捕获,如下:

    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        if (this.applicationContext.ehttp://www.devze.comquals(event.getSource())
              // Backwards compatible
              || event.getKeys().equals(event.getSource())) {
           rebind();
        }
    }
    

    然后rebind方法被调用:

    @ManagedOperation
    public void rebind() {
        this.errors.clear();
        for (String name : this.beans.getBeanNames(www.devze.com)) {
           rebind(name);
        }
    }
    

    这里beans变量的类型是:ConfigurationPropertiesBeans,它里面保存了所有待刷新的ConfigurationProperties的bean。

    结论

    因为启动阶段中,MyProp优先于ConfigurationPropertiesBeans被加载,导致ConfigurationPropertiesBeans里面不会包含MyProp这个bean,从而导致它不会被刷新。

    以上就是Spring应用中使用acutator/refresh刷新属性不生效的问题分析及解决的详细内容,更多关于Spring使用acutator/refresh不生效的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消