Nacos @RefreshScope 配置不生效问题

对于Spring-Cloud 集成Nacos作为配置中心,集成比较简单,而且使用只需在bean上增加@RefreshScope注解即可。

入门实例参考官网:quick-start

按照官网配置在controller中使用@Value注解的变量,并使用@RefreshScope注解对应的Controller,通过Nacos配置界面更改配置值后,访问controller接口可以实时生效。

但是,如果采用该方式直接在一个service或者component中定义动态配置,并且仅在service或者component内使用类内部定义的动态配置,则会出现失效问题!

问题演示:启动服务后,通过nacos修改配置print.paused=true,不生效。

@Slf4j
@RefreshScope
@Component
public class HelloHandler implements InitializingBean {
 
    @Value("${print.paused:false}")
    private volatile boolean paused;
 
    private final ThreadPoolExecutor executor = ThreadUtil.newPool(1, 1, 0L, 1, "hello_thread");
 
    @Override
    public void afterPropertiesSet() {
        this.executor.submit(this::hello);
    }
 
    private void hello() {
        while (true) {
            if (paused) {
                log.info("borker paused.");
                break;
            }
            try {
                System.out.println("hello world");
                ThreadUtil.sleep(5000);
            } catch (Exception e) {
                log.error("error => {}", e.getMessage());
                ThreadUtil.sleep(5000);
            }
        }
    }
}


跟踪代码发现nacos发布的配置可以正常被服务接收到,并且最终通过ContextRefresher.refresh()方法进行了刷新操作,refresh() -> org.springframework.cloud.context.refresh.RefreshScope.refreshAll()方法。并发布RefreshScopeRefreshedEvent事件,

在这之间通过ConfigurationPropertiesRebinder重新绑定了环境变量的properties对应的bean,先销毁后创建,debug发现通过配置的Nacos dataID: demo-dev.properties对应的PropertySource中存在已经发布更新的变量。但是HelloHandler中的变量依然是未更新的。

到此处出现第一种方式,就是将配置变量提出来单独作为一个HelloProperties类,使用@ConfigurationProperties注解标注配置类,在通过Nacos发布配置后,配置类会被重新rebind,此时变量动态修改生效,使用的地方@EnableConfigurationProperties,通过@Autowired注入。

@Data
@ConfigurationProperties("print")
public class HelloProperties {
    @Value("${paused:false}")
    private volatile boolean paused;
}


后来通过查阅资料,在通过@RefreshScope注解的类会在context启动时创建一个动态代理类,

 ClassPathBeanDefinitionScanner.doScan(..packageName) -> AnnotationConfigUtils.applyScopedProxyMode(..) 该类会判断BeanDefinition的scope为ScopedProxyMode.TARGET_CLASS的bean为其创建动态代理,代理类为ScopedProxyFactoryBean是个FactoryBean。而RefreshScope类继承GenericScope,在postProcessBeanDefinitionRegistry中会对该类进行判断,如果判断是ScopedProxyFactoryBean,将其beanClass设置为LockedScopedProxyFactoryBean是ScopedProxyFactoryBean的子类同时也是FactoryBean,所以最终获取实际被代理的类就是调用LockedScopedProxyFactoryBean的getObject类,debug发现getObject返回了一个proxy,该proxy是SimpleBeanTargetSource,该类每次从容器中获取被代理的bean,获取时首先经过GenericScope.get(...)获取,scoped的bean被缓存在一个Map中,在发布动态配置时会清空map重新创建一个bean放到map中,重新创建的bean变量就是动态获取到的最新值。而重建的bean是动态代理前缀为scopedTarget.的bean,被wrap的原始bean不会发生变化,所以配置不会被更新。

该代理类生成的bean名称为:scopedTarget.helloHandler, 在原beanName基础上加了个 scopedTarget. 前缀,而原bean的名称还是helloHandler, 而在原bean中执行的代码获取的变量就是一开始的默认值,并不会动态刷新。通过idea的beans插件或者actuator监控可以发现HelloHandler类型的bean有两个,一个是helloHandler,一个是scopedTarget.helloHandler,每次发布更新配置时,生效的是scopedTarget.helloHandler,如果其他地方有Autowired使用helloHandler bean,每次获取时最终通过RefreshScope.get(x..)获取到的是scopedTarget.helloHandler,而bean本身内部获取内部动态属性时获取的是helloHandler的所以不生效。

第二种方式将属性提出来最为HelloProperties并且使用@RefreshScope+@Component注解,在需要使用的地方直接@Autowired注入即可。

@Data
@Component
@RefreshScope
public class HelloProperties {
    @Value("${print.paused:false}")
    private volatile boolean paused;
}
@Configuration注解初始化的bean动态更新

// configuration 配置类
@Configuration
public class TestConfiguration {
 
    @Bean
    @RefreshScope
    public TestBean test(@Value("${test:hello}") String name) {
        return new TestBean(name);
    }
 
}
 
// bean
@Slf4j
@Data
public class TestBean {
    private String name;
 
    public TestBean(String name) {
        this.name = name;
    }
 
    public void print() {
      log.info("print => {}", name);
    }
}
 
// controller
 
@RestController
@RequestMapping("test")
public class TestController {
 
    @Autowired
    private TestBean bean;
 
    @GetMapping("name")
    public String test() {
        bean.print();
        return bean.getName();
    }
}


 
原文链接:https://blog.csdn.net/fly_leopard/article/details/107489937 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值