【Apollo】支持@ConfigurationProperties动态刷新

Apollo支持@ConfigurationProperties动态刷新

问题

apollo动态刷新,应用在@value这种注入方式的属性没有问题,但是如果使用@ConfigurationProperties注解的bean,动态刷新就不好使了,会注入不到的。

Apollo动态刷新官网介绍

@Value,动态刷新
@ConfigurationProperties, 需要添加apollo配置监听器@ApolloConfigChangeListener实现动态刷新

官方文档原话:

需要注意的是,@ConfigurationProperties如果需要在Apollo配置变化时自动更新注入的值,需要配合使用EnvironmentChangeEventRefreshScope。相关代码实现,可以参考apollo-use-cases项目中的ZuulPropertiesRefresher.java和apollo-demo项目中的SampleRedisConfig.java以及SpringBootApolloRefreshConfig.java

@Value:直接属性映射

@ConfigurationProperties:bean属性映射的解决办法

@ConfigurationProperties及yml

my.config:
  age: 12
  name: #{first-name: lao, last-name: liu}
    first-name: lao
    last-name:  liu
  map:  #{eq: 90, iq: 85}
    eq: '09' #不加引号会转化成9,加上则转成09
    iq: 85
  list: #[吃喝睡, 打豆豆]
    - 吃喝睡
    - 打豆豆
@Data
@Configuration
@ConfigurationProperties(prefix = "my.config")
public class MyConfigProperties {
    private Integer age;
    private Name name;
    private Map<String, Integer> map;
    private List<String> list;

    @Data
    public static class Name {
        private String firstName;
        private String lastName;
    }
}

方式一: EnvironmentChangeEvent + @ApolloConfigChangeListener

  1. 新建一个监听类:
@Slf4j
@Configuration
public class ApolloChangeListener {
    @Autowired
    private ApplicationContext applicationContext;

    @ApolloConfigChangeListener(value = "application.yml", interestedKeyPrefixes = "my.config.")
    public void refresh(ConfigChangeEvent changeEvent) {
        log.info("Changes for namespace " + changeEvent.getNamespace());
        for(String key : changeEvent.changedKeys()) {
            ConfigChange change = changeEvent.getChange(key);
            log.info(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));
        }
        // 更新相应的bean的属性值,主要是存在@ConfigurationProperties注解的bean
        applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
        // 相关业务处理
        // ...
    }
}
方式一的坑

Apollo动态刷新后, MyConfigProperties不同数据类型实时刷新的情况如下:

数据类型/Apollo操作 新增key,val 更新val 增改2维key,val 删除2维key,val 清空val 删除key
Integer age - - × ×
Name name × × ×
Map map × × ×
List list

说明: √: 可以实现; ×: 不可实现; -: 无此场景;
结论: 使用EnvironmentChangeEvent来实现Apollo动态刷新@ConfigurationProperties注解的bean,存在未全量覆盖的问题,需考虑使用场景酌情使用.

方式二(完美方案): RefreshScope + @ApolloConfigChangeListener

  1. 被@ConfigurationProperties修饰的类需要添加如下注解:
@Component
@RefreshScope
  1. 新建一个监听类:
@Slf4j
@Configuration
public class ApolloChangeListener {
    @Autowired
    private RefreshScope refreshScope;

    @ApolloConfigChangeListener(value = "application.yml", interestedKeyPrefixes = "my.config.")
    public void refresh(ConfigChangeEvent changeEvent) {
        log.info("Changes for namespace " + changeEvent.getNamespace());
        for(String key : changeEvent.changedKeys()) {
            ConfigChange change = changeEvent.getChange(key);
            log.info(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));
        }
        // 刷新的bean
        refreshScope.refreshAll();
        // 刷新指定的bean
//        refreshScope.refresh("myConfigProperties");
        // ...
    }
}

监听触发后会刷新@ConfigurationProperties注解的bean, 重新赋值, 完美解决方式一的坑.

方式三: 自定义 + @ApolloConfigChangeListener

  1. 新建一个监听类:
@Slf4j
@Configuration
public class ApolloChangeConfig {

    @ApolloConfigChangeListener(value = "application.yml", interestedKeyPrefixes = "my.config.")
    public void refresh(ConfigChangeEvent changeEvent) {
        log.info("Changes for namespace " + changeEvent.getNamespace());
        for(String key : changeEvent.changedKeys()) {
            ConfigChange change = changeEvent.getChange(key);
            log.info(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));
            //如果改的是配置类的参数,则手动修改配置
            if (key.startsWith("age")) {
                //根据变更类型进行添加或删除操作
                switch (change.getChangeType()) {
                    case ADDED:
                        this.add(change.getPropertyName(), change.getNewValue());
                        break;
                    case MODIFIED:
                        this.put(change.getPropertyName(), change.getNewValue());
                        break;
                    case DELETED:
                        this.remove(change.getPropertyName());
                        break;
                    default:
                        break;
                }
            }
        }
    }
}
redis配置动态刷新成功了,但起作用的仍是旧配置,需手动重新建立链接才能生效(Ribbon也类似).

额外说明:
有时候,动态刷新成功后,发现有些调用相关接口执行还是原配置的效果,没成功(比如某个花钱的配置属性的环境切换,pro改为test,调用相关接口发现执行时属性还是pro时的效果)。
那有可能是因为涉及到配置类启动时初始化的问题,比如说,有个注入到spring容器的配置类(@Configuration@bean)刚好用到你这个带有@ConfigurationProperties类在项目启动进行初始化。项目启动后,已经注入到了spring容器中,你动态刷新的配置属性值,但是没有去把容器中的这个配置类再次刷新,那么配置类还是原来的配置类,里面的属性也还是原来的。
解决方法:在上面相关业务处理下面,也就是配置动态刷新后,再次初始化一下spring容器中的那个配置类,就能生效成功了。
(可能表达的不是很好。。。,说白了可能就是调用到相关配置属性另一个bean没有刷新)

@ApolloConfigChangeListener介绍

@ApolloConfigChangeListener()注解中可填写参数
value:指定监听的配置文件。
interestedKeyPrefixes:指定监听的配置属性前缀。当在Apollo配置中心修改配置,配置发生变化的前缀是所填值,那么就执行带有@ApolloConfigChangeListener()注解方法。
注:如果不填写属性值,默认是只要发生变化都执行方法

举例:
@ApolloConfigChangeListener():默认监听的是application命名空间。
@ApolloConfigChangeListener("mysql-config"):指定mysql-config命名空间。
@ApolloConfigChangeListener("${apollo.bootstrap.namespaces}"):指定参数所配置的命名空间(多个用逗号分隔)。
@ApolloConfigChangeListener(value = "application.yml", interestedKeyPrefixes = "my.config."):当在Apollo配置中心,修改application.yml下的,前缀为my.config的属性时,触发刷新方法执行。
@ApolloConfigChangeListener(value = {"application.yml","MySQL"}, interestedKeyPrefixes = {"spring.","my.config"}):当在Apollo配置中心,修改application.ymlMySQL.properties下的,前缀为spring.my.config的属性时,触发刷新方法执行。

参考文档

  1. Apollo修改配置文件后运行中的项目使用@ConfigurationProperties的配置实时更新

  2. @ConfigurationProperties 读取Apollo 修改配置不生效

  3. github - 官方参考

  4. 简书 - Apollo配置中心遇到的坑

  5. [https://blog.csdn.net/weixin_43453386/article/details/112787765](CSDN - Apollo客户端监听配置变化、动态刷新)

  6. Apollo的github

  7. Apollo配置中心设计

  8. Apollo使用指南

  9. Java客户端使用指南

你可能感兴趣的:(Apollo,SpringBoot,Apollo,动态刷新)