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

 更新时间:2024年01月10日 11:43:28   作者:龙鹤鹿  
在Spring应用收到/actuator/refresh的POST请求后,标注了@RefreshScope以及@ConfiguratioinProperties的bean会被Spring容器重新加载,但是,在实际应用中,并没有按照预期被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,是一个用来注册所有标注了@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 : candidateAdvisors) {
       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完成初始化。

应用运行阶段

在应用运行阶段,当收到/actuator/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.equals(event.getSource())
          // Backwards compatible
          || event.getKeys().equals(event.getSource())) {
       rebind();
    }
}

然后rebind方法被调用:

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

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

结论

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

以上就是Spring应用中使用acutator/refresh刷新属性不生效的问题分析及解决的详细内容,更多关于Spring使用acutator/refresh不生效的资料请关注脚本之家其它相关文章!

相关文章

  • JAVA中的延迟队列DelayQueue应用解析

    JAVA中的延迟队列DelayQueue应用解析

    这篇文章主要介绍了JAVA中的延迟队列DelayQueue应用解析,DelayQueue是一个根据元素的到期时间来排序的队列,而并非是一般的队列那样先进先出,最快过期的元素排在队首,越晚到期的元素排得越后,需要的朋友可以参考下
    2023-12-12
  • Java web.xml之contextConfigLocation作用案例详解

    Java web.xml之contextConfigLocation作用案例详解

    这篇文章主要介绍了Java web.xml之contextConfigLocation作用案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Java获取音频文件的持续时间的实现方案

    Java获取音频文件的持续时间的实现方案

    在音视频处理开发中,FFmpeg 一直是最常用的跨平台音视频处理工具,然而在很多实际场景中,FFmpeg 并不总是理想的选择,本文将展示一种完全不依赖 FFprobe / FFmpeg 的方案,使用 Java 自带的 javax.sound.sampled API 来获取音频文件的持续时间,需要的朋友可以参考下
    2025-11-11
  • Java重入锁(ReentrantLock)从入门到源码深度解析

    Java重入锁(ReentrantLock)从入门到源码深度解析

    本文给大家介绍Java重入锁(ReentrantLock)从入门到源码深度解析,通过本文学习可以全方位地认识ReentrantLock,从基本概念到高级特性,从使用方式到源码剖析,从底层原理到实际应用,感兴趣的朋友跟随小编一起看看吧
    2026-02-02
  • 用Java设计模式中的观察者模式开发微信公众号的例子

    用Java设计模式中的观察者模式开发微信公众号的例子

    这篇文章主要介绍了用Java设计模式中的观察者模式开发微信公众号的例子,这里Java的微信SDK等部分便不再详述,只注重关键部分和开发过程中观察者模式优点的体现,需要的朋友可以参考下
    2016-02-02
  • Java中CopyOnWriteArrayList的使用解析

    Java中CopyOnWriteArrayList的使用解析

    这篇文章主要介绍了Java中CopyOnWriteArrayList的使用解析,CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存,它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用,需要的朋友可以参考下
    2023-12-12
  • 深入解析java中的locale

    深入解析java中的locale

    以下是对java中的locale进行了深入的分析介绍,需要的朋友可以过来参考下
    2013-08-08
  • Spring boot基于ScheduledFuture实现定时任务

    Spring boot基于ScheduledFuture实现定时任务

    这篇文章主要介绍了Spring boot基于ScheduledFuture实现定时任务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • JAVA的Dubbo如何实现各种限流算法

    JAVA的Dubbo如何实现各种限流算法

    Dubbo是一种高性能的Java RPC框架,广泛应用于分布式服务架构中,在Dubbo中实现限流可以帮助服务在高并发场景下保持稳定性和可靠性,常见的限流算法包括固定窗口算法、滑动窗口算法、令牌桶算法和漏桶算法,在Dubbo中集成限流器可以通过实现自定义过滤器来实现
    2025-01-01
  • Java Shutdown Hook场景使用及源码分析

    Java Shutdown Hook场景使用及源码分析

    shutdown hook 就是一个简单的已初始化但是未启动的线程,本文详细的介绍了Java Shutdown Hook场景使用及源码分析,感兴趣的朋友可以参考一下
    2021-06-06

最新评论