apollo更改配置刷新@ConfigurationProperties配置类
作者:王者之峰 发布时间:2022-01-09 22:28:22
前言
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;
}
}
来源:https://juejin.cn/post/7218554163050430521
猜你喜欢
- controller传boolean形式值@GetMapping("/check-cart")public List&l
- 最近在做项目的时候有用到对两个集合中的元素进行对比求其交集的情况,因为涉及到的数据量比较大,所以在进行求两个集合中元素交集的时候,就应该考虑
- Java 本身就自带 JS 引擎,自从 Java 1.6 开始就支持了,愈来愈好。我对 js 比较熟悉,因此有个大胆的想法,为什么不用自带
- mybatis-plus的代码生成器会在实体类中生成数据库所有字段,我们去用mapper接口查询时,会返回数据库所有的字段。但有些字段不是我
- 本文实例讲述了Java实现的Base64加密算法。分享给大家供大家参考,具体如下:一 算法实现1、JDK2、Commonc Codec3、B
- 一、SpringBoot 指定配置文件路径:在 SpringBoot 中,可以将配置文件放在 jar 包外面,这样可以方便地修改配置而不需要
- 目录为什么要实现调用链跟踪?如何实现?第一步,看图、看场景,用户浏览器的一次请求行为所走的路径是什么样的第二步,实现。不想看代码可直接拉最后
- static :静态常量,静态方法,静态代码块静态变量: 静态变量属于类的,使用类名来访问,非静态变量是属于对象的,"必须&quo
- 背景公司线上有个tomcat服务,里面合并部署了大概8个微服务,之所以没有像其他微服务那样单独部署,其目的是为了节约服务器资源,况且这8个服
- 本文实例讲述了java继承中的构造方法。分享给大家供大家参考。具体如下:继承中的构造方法: 1、子类的构造过程中必须调用其基类的构造方法。2
- 利用Java语言中的集合、Swing、线程等知识点编写一个坦克大战游戏。(1) 画出敌我坦克的原理:在坦克类里面有一个布尔类型变量good。
- 一:hibernate-validator 基础1. 简介:通过使用注解Annotations 给类或者类的属性加上约束(constrain
- 为什么需要 StreamStream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 Output
- 1. 什么是对象池对象池,顾名思义就是一定数量的已经创建好的对象(Object)的集合。当需要创建对象时,先在池子中获取,如果池子中没有符合
- package com.test; import java.io.BufferedReader; import jav
- IoC的概念介绍控制反转(IOC)模式(又称DI:Dependency Injection)就是Inversion of Control,控
- 一、线程的优先级别线程优先级别的使用范例:package cn.galc.test;public class TestThread6 { p
- 本文实例讲述了Java文件操作工具类fileUtil。分享给大家供大家参考,具体如下:package com.gcloud.common;i
- Java项目涉及到数据库交互,以往常用的是JDBC,现在则有Hibernate、Mybatis等这些持久化支持。项目中用到了MyBatis,
- Spring Security 过滤器链及自定义Filter别名类名称Namespace Element or AttributeCHANN