apollo更改配置刷新@ConfigurationProperties配置类
作者:王者之峰
前言
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!"); /** * rebind configuration beans, e.g. ZuulProperties * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent */ this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); /** * refresh routes * @see org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration.ZuulRefreshListener#onApplicationEvent */ 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事件进行更新。
/* * Copyright 2022 Apollo Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ 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); } }
/* * Copyright 2022 Apollo Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ 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; /** * You may set up data like the following in Apollo: <br /><br /> Properties Sample: * application.properties * <pre> * redis.cache.enabled = true * redis.cache.expireSeconds = 100 * redis.cache.clusterNodes = 1,2 * redis.cache.commandTimeout = 50 * redis.cache.someMap.key1 = a * redis.cache.someMap.key2 = b * redis.cache.someList[0] = c * redis.cache.someList[1] = d * </pre> * * Yaml Sample: application.yaml * <pre> * redis: * cache: * enabled: true * expireSeconds: 100 * clusterNodes: 1,2 * commandTimeout: 50 * someMap: * key1: a * key2: b * someList: * - c * - d * </pre> * * To make <code>@ConditionalOnProperty</code> work properly, <code>apollo.bootstrap.enabled</code> * should be set to true and <code>redis.cache.enabled</code> should also be set to true. Check * 'src/main/resources/application.yml' for more information. * */ @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()
默认监听application
namespace,我们需要只监听项目依赖的的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; /** * 注册configChangeListener监听指定的NameSpace,默认的业务配置都在与应用名命名的nameSpace,当然了如果希望监听到更多的自己去拿到配置的nameSpace也可以的 */ @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); }); } /** * register beanName with ConfigurationProperties#prefix if * annotation @ConfigurationProperties and @RefreshScope is existed * * @param bean * @param beanName * @return * @throws BeansException */ @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; } /** * 防止可能出现的匹配到短prefix的情况,例如 key = "a.ab.abc", prefixA = "a", prefixB = "a.ab.abc",匹配到prefixA返回的情况,这里需要得到最匹配 * * @param key * @return beanName best match key */ 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的资料请关注脚本之家其它相关文章!