apollo更改配置刷新@ConfigurationProperties配置类

2023-05-14 17:05:58 配置 更改 刷新

前言

apollo配置经常使用的方式是@value,比较便捷,如果只出现在一个类中还行,但是如果多个类中并不是很方便,特别是如果出现配置值变化了之后需要触发相关变动也无法实现,因此就会考虑使用配置类@ConfigurationProperties,它能实现:

  • 统一管理一组配置。
  • 配置变化的时需要触发相关变动只涉及一个bean。

但是apollo配置变化时不会把@ConfigurationProperties的值进行更新,具体看官方文档,需要配合EnvironmentChangeEvent或RefreshScope使用。

可以大概看看:

package com.ctrip.framework.apollo.use.cases.spring.cloud.zuul;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ZuulPropertiesRefresher implements ApplicationContextAware {
  private static final Logger logger = LoggerFactory.getLogger(ZuulPropertiesRefresher.class);
  private ApplicationContext applicationContext;
  @Autowired
  private RouteLocator routeLocator;
  @ApolloConfigChangeListener(interestedKeyPrefixes = "zuul.")
  public void onChange(ConfigChangeEvent changeEvent) {
    refreshZuulProperties(changeEvent);
  }
  private void refreshZuulProperties(ConfigChangeEvent changeEvent) {
    logger.info("Refreshing zuul properties!");
    
    this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
    
    this.applicationContext.publishEvent(new RoutesRefreshedEvent(routeLocator));
    logger.info("Zuul properties refreshed!");
  }
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}

使用@ApolloConfigChangeListener注册了apollo配置变化监听器,内部使用了cloud发布EnvironmentChangeEvent事件进行更新。


package com.apolloconfig.apollo.demo.SpringBoot.refresh;
import com.apolloconfig.apollo.demo.springboot.config.SampleRedisConfig;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.stereotype.Component;
@ConditionalOnProperty("redis.cache.enabled")
@Component
public class SpringBootApolloRefreshConfig {
  private static final Logger logger = LoggerFactory.getLogger(SpringBootApolloRefreshConfig.class);
  private final SampleRedisConfig sampleRedisConfig;
  private final RefreshScope refreshScope;
  public SpringBootApolloRefreshConfig(
      final SampleRedisConfig sampleRedisConfig,
      final RefreshScope refreshScope) {
    this.sampleRedisConfig = sampleRedisConfig;
    this.refreshScope = refreshScope;
  }
  @ApolloConfigChangeListener(value = "${listeners}", interestedKeyPrefixes = {"redis.cache."})
  public void onChange(ConfigChangeEvent changeEvent) {
    logger.info("before refresh {}", sampleRedisConfig.toString());
    refreshScope.refresh("sampleRedisConfig");
    logger.info("after refresh {}", sampleRedisConfig);
  }
}

package com.apolloconfig.apollo.demo.springboot.config;
import com.Google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

@ConditionalOnProperty("redis.cache.enabled")
@ConfigurationProperties(prefix = "redis.cache")
@Component("sampleRedisConfig")
@RefreshScope
public class SampleRedisConfig implements InitializingBean {
  private static final Logger logger = LoggerFactory.getLogger(SampleRedisConfig.class);
  private int expireSeconds;
  private String clusternodes;
  private int commandTimeout;
  private Map<String, String> someMap = Maps.newLinkedHashMap();
  private List<String> someList = Lists.newLinkedList();
  @Override
  public void afterPropertiesSet() throws Exception {
    logger.info(
        "SampleRedisConfig initialized - expireSeconds: {}, clusterNodes: {}, commandTimeout: {}, someMap: {}, someList: {}",
        expireSeconds, clusterNodes, commandTimeout, someMap, someList);
  }
  public void setExpireSeconds(int expireSeconds) {
    this.expireSeconds = expireSeconds;
  }
  public void setClusterNodes(String clusterNodes) {
    this.clusterNodes = clusterNodes;
  }
  public void setCommandTimeout(int commandTimeout) {
    this.commandTimeout = commandTimeout;
  }
  public Map<String, String> getSomeMap() {
    return someMap;
  }
  public List<String> getSomeList() {
    return someList;
  }
  @Override
  public String toString() {
    return String.fORMat(
        "[SampleRedisConfig] expireSeconds: %d, clusterNodes: %s, commandTimeout: %d, someMap: %s, someList: %s",
        expireSeconds, clusterNodes, commandTimeout, someMap, someList);
  }
}

使用了RefreshScope进行刷新配置(重新create bean),但是不够高效,最理想的事一个值发生变化只要重新把对应的属性set即可。

那么我们下面尝试下,需要解决问题:

  • 确定@ApolloConfigChangeListener.value能不能填*表示匹配所有namespace。
  • 根据ConfigChangeEvent找到配置类及对应的成员进行set。

解决@ApolloConfigChangeListener监听所有namespace

@ApolloConfigChangeListener(value = "*")
public void onChange(ConfigChangeEvent changeEvent) {
    log.info("onChange changeEvent:{}", changeEvent);
}

发现并不行。应该可以编程式添加listener

编程式添加listener

找到官方文档:

Config config = ConfigService.getAppConfig();
config.addChangeListener(new ConfigChangeListener() {
  @Override
  public void onChange(ConfigChangeEvent changeEvent) {
    for (String key : changeEvent.changedKeys()) {
      ConfigChange change = changeEvent.getChange(key);
      System.out.println(String.format(
        "Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s",
        change.getPropertyName(), change.getOldValue(),
        change.getNewValue(), change.getChangeType()));
        }
    }
});

ConfigService.getAppConfig()默认监听applicationnamespace,我们需要只监听项目依赖的的namespace。

获取项目依赖的的namespace

google了一下发现:apollo配置中心之--Spring Boot如何加载apollo。 可以通过environment获取key为ApolloPropertySources,我们尝试下:

@PostConstruct
public void reGISterApolloConfigChangeListener() {
    //从env中拿到所有已从Apollo加载的propertySource,获取监听的nameSpace
    CompositePropertySource apolloPropertySources = (CompositePropertySource) configurableEnvironment.getPropertySources().get("ApolloPropertySources");
    if (Objects.isNull(apolloPropertySources)) {
        return;
    }
    Collection<PropertySource<?>> propertySourceList = apolloPropertySources.getPropertySources();
    //注册监听所有加载的nameSpace
    propertySourceList.forEach(propertySource -> {
        ConfigChangeListener configChangeListener = changeEvent -> {
            for (String changedKey : changeEvent.changedKeys()) {
                ConfigChange change = changeEvent.getChange(changedKey);
                System.out.println(String.format(
                        "Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s",
                        change.getPropertyName(), change.getOldValue(),
                        change.getNewValue(), change.getChangeType()));
            }
        };
        Config config = ConfigService.getConfig(propertySource.getName());
        config.addChangeListener(configChangeListener);
    });
}

寻找根据ConfigChangeEvent找到配置类及对应的成员进行set的方式

google了一下,没找到,但是spring肯定有相关的实现,比如读取yml后对 @ConfigurationProperties属性进行填充,通过这篇文章找到ConfigurationPropertiesBindingPostProcessor是核心处理类:

通过ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization进行赋值,下面我们来看看能不能直接使用它:

package com.onepiece.apollo;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@Slf4j
@Component
public class ApolloRefreshConfig implements BeanPostProcessor, Ordered {
    @Resource
    private ConfigurableEnvironment configurableEnvironment;
    private Map<String, String> configPrefixBeanNameMapping;
    @Resource
    private ConfigurationPropertiesBindingPostProcessor configurationPropertiesBindingPostProcessor;
    @Resource
    private ApplicationContext applicationContext;
    
    @PostConstruct
    public void registerApolloConfigChangeListener() {
        //从env中拿到所有已从Apollo加载的propertySource,获取监听的nameSpace
        CompositePropertySource apolloPropertySources = (CompositePropertySource) configurableEnvironment.getPropertySources().get("ApolloPropertySources");
        if (Objects.isNull(apolloPropertySources)) {
            return;
        }
        Collection<PropertySource<?>> propertySourceList = apolloPropertySources.getPropertySources();
        //注册监听所有加载的nameSpace
        propertySourceList.forEach(propertySource -> {
            ConfigChangeListener configChangeListener = changeEvent -> {
                for (String changedKey : changeEvent.changedKeys()) {
                    log.info("apollo changed namespace:{} Key:{} value:{}", changeEvent.getNamespace(), changedKey, changeEvent.getChange(changedKey));
                    String beanName = getBeanName(changedKey);
                    configurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(applicationContext.getBean(beanName), beanName);
                }
            };
            Config config = ConfigService.getConfig(propertySource.getName());
            config.addChangeListener(configChangeListener);
        });
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        ConfigurationProperties propertiesAnno = bean.getClass().getAnnotation(ConfigurationProperties.class);
        if (propertiesAnno != null) {
            if (configPrefixBeanNameMapping == null) {
                configPrefixBeanNameMapping = Maps.newHashMap();
            }
            String prefix = propertiesAnno.prefix() != null ? propertiesAnno.prefix() : propertiesAnno.value() == null ? null : propertiesAnno.value();
            if (StringUtils.isNotBlank(prefix) && StringUtils.isNotBlank(beanName)) {
                this.configPrefixBeanNameMapping.put(prefix, beanName);
            }
        }
        return bean;
    }
    @Override
    public int getOrder() {
        return 0;
    }
    
    private String getBeanName(String key) {
        if (configPrefixBeanNameMapping != null) {
            Optional<Map.Entry<String, String>> bestMatchEntry = configPrefixBeanNameMapping.entrySet().stream()
                    .filter(entryt -> key.startsWith(entryt.geTKEy() + "."))
                    .max(Comparator.comparing(Map.Entry<String, String>::getKey));
            return bestMatchEntry.map(Map.Entry::getValue).orElse(null);
        }
        return null;
    }
}

以上就是apollo更改配置刷新@ConfigurationProperties配置类的详细内容,更多关于apollo刷新@ConfigurationProperties的资料请关注其它相关文章!

相关文章