本文讲述apollo在springboot项目中如何实现自动刷新
1.本文通过apollo在github上的apollo-demo项目来讲解
github地址:https://github.com/ctripcorp/apollo
2.代码解析
2.1 启动类代码解析
@SpringBootApplication(scanBasePackages = {"com.ctrip.framework.apollo.demo.spring.common",
"com.ctrip.framework.apollo.demo.spring.springBootDemo"
})
public class SpringBootSampleApplication {
public static void main(String[] args) throws IOException {
ApplicationContext context = new SpringApplicationBuilder(SpringBootSampleApplication.class).run(args);
AnnotatedBean annotatedBean = context.getBean(AnnotatedBean.class);
SampleRedisConfig redisConfig = null;
try {
redisConfig = context.getBean(SampleRedisConfig.class);
} catch (NoSuchBeanDefinitionException ex) {
System.out.println("SampleRedisConfig is null, 'redis.cache.enabled' must have been set to false.");
}
System.out.println("SpringBootSampleApplication Demo. Input any key except quit to print the values. Input quit to exit.");
while (true) {
System.out.print("> ");
String input = new BufferedReader(new InputStreamReader(System.in, Charsets.UTF_8)).readLine();
if (!Strings.isNullOrEmpty(input) && input.trim().equalsIgnoreCase("quit")) {
System.exit(0);
}
System.out.println(annotatedBean.toString());
if (redisConfig != null) {
System.out.println(redisConfig.toString());
}
}
}
}
这里主要通过上下文去获取bean,然后测试是否能拿到apollo的配置。
2.2 AnnotatedBean类代码解析
@Component("annotatedBean")
public class AnnotatedBean {
private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class);
private int timeout;
private int batch;
private List<JsonBean> jsonBeans;
/**
* ApolloJsonValue annotated on fields example, the default value is specified as empty list - []
* <br />
* jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]
*/
@ApolloJsonValue("${jsonBeanProperty:[]}")
private List<JsonBean> anotherJsonBeans;
@Value("${batch:100}")
public void setBatch(int batch) {
logger.info("updating batch, old value: {}, new value: {}", this.batch, batch);
this.batch = batch;
}
@Value("${timeout:200}")
public void setTimeout(int timeout) {
logger.info("updating timeout, old value: {}, new value: {}", this.timeout, timeout);
this.timeout = timeout;
}
/**
* ApolloJsonValue annotated on methods example, the default value is specified as empty list - []
* <br />
* jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]
*/
@ApolloJsonValue("${jsonBeanProperty:[]}")
public void setJsonBeans(List<JsonBean> jsonBeans) {
logger.info("updating json beans, old value: {}, new value: {}", this.jsonBeans, jsonBeans);
this.jsonBeans = jsonBeans;
}
@Override
public String toString() {
return String.format("[AnnotatedBean] timeout: %d, batch: %d, jsonBeans: %s", timeout, batch, jsonBeans);
}
private static class JsonBean{
private String someString;
private int someInt;
@Override
public String toString() {
return "JsonBean{" +
"someString='" + someString + '\'' +
", someInt=" + someInt +
'}';
}
}
}
这个类主要甘肃我们 apollo的注解@ApolloJsonValue和spring的注解@Value都是可以拿到apollo的配置的,只是ApolloJsonValue可以解析json对象
2.4 SampleRedisConfig代码解析
@ConditionalOnProperty("redis.cache.enabled")
@ConfigurationProperties(prefix = "redis.cache")
@Component("sampleRedisConfig")
@RefreshScope
public class SampleRedisConfig {
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();
@PostConstruct
private void initialize() {
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);
}
}
我们也可以通过@ConfigurationProperties注解,通过前缀的方式拿到apollo的配置,set bean的属性。
3.测试
先直接启动看下效果,按下回车,控制台输出:
[AnnotatedBean] timeout: 200, batch: 100, jsonBeans: []
3.1 配置apollo
在配置文件中,配置
app:
id: 你的appid
然后修改MetaDomainConsts中的 DEFAULT_META_URL 常量(你的meta server地址)
并在apollo后台新增如下配置:
timeout: 1000, batch: 200,
jsonBeanProperty: [{omeString: '111',omeInt: 222}],
redis.cache.expireSeconds: 55555
再次启动看效果,控制台输出:
[AnnotatedBean] timeout: 1000, batch: 200, jsonBeans: [JsonBean{someString='111', someInt=222}]
AnnotatedBean的属性值都拿到了,但是SampleRedisConfig中的expireSeconds属性并没有拿到
3.2 @ConditionalOnProperty注解
Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效
在看下SampleRedisConfig类上有@ConditionalOnProperty(“redis.cache.enabled”),恍然大悟,需要配置redis.cache.enabled中的属性为true才会生效,所以我们在后台继续新增配置redis.cache.enabled:true。
这是控制台输出结果如下
[AnnotatedBean] timeout: 1000, batch: 200, jsonBeans: [JsonBean{someString='111', someInt=222}]
[SampleRedisConfig] expireSeconds: 55555, clusterNodes: null, commandTimeout: 1111, someMap: {}, someList: []
4 实时刷新
4.1通过后台修改配置
timeout:2000,expireSeconds:88888
看控制台立马输出了日志
[apollo-demo][Apollo-Config-6]2020-03-08 13:47:16,334 INFO [com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean] updating timeout, old value: 1000, new value: 2000
[apollo-demo][Apollo-Config-6]2020-03-08 13:47:16,334 INFO [com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener] Auto update apollo changed value successfully, new value: 2000, key: timeout, beanName: annotatedBean, method: com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean.setTimeout
[apollo-demo][Apollo-Config-2]2020-03-08 13:32:34,605 INFO [com.ctrip.framework.apollo.demo.spring.springBootDemo.refresh.SpringBootApolloRefreshConfig] before refresh [SampleRedisConfig] expireSeconds: 55555, clusterNodes: null, commandTimeout: 1111, someMap: [SampleRedisConfig] expireSeconds: 55555, clusterNodes: null, commandTimeout: 1111, someMap: {}, someList: [], someList: []
[apollo-demo][Apollo-Config-2]2020-03-08 13:32:34,609 INFO [com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig] SampleRedisConfig initialized - expireSeconds: 88888, clusterNodes: null, commandTimeout: 1111, someMap: 88888, someList: []
[apollo-demo][Apollo-Config-2]2020-03-08 13:32:34,609 INFO [com.ctrip.framework.apollo.demo.spring.springBootDemo.refresh.SpringBootApolloRefreshConfig] after refresh [SampleRedisConfig] expireSeconds: 88888, clusterNodes: null, commandTimeout: 1111, someMap: [SampleRedisConfig] expireSeconds: 88888, clusterNodes: null, commandTimeout: 1111, someMap: {}, someList: [], someList: []
回车空控制台输出
[AnnotatedBean] timeout: 2000, batch: 200, jsonBeans: [JsonBean{someString='111', someInt=222},]
[SampleRedisConfig] expireSeconds: 88888, clusterNodes: null, commandTimeout: 1111, someMap: {}, someList: [
发现timeout的值和expireSeconds的值实时更新了
4.2 SpringBootApolloRefreshConfig类
值得注意的是,在官方文档中是这样介绍的
@ConfigurationProperties如果需要在Apollo配置变化时自动更新注入的值,需要配合使用EnvironmentChangeEvent或RefreshScope
4.3 客户端设计
1.客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)
2.客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
这是一个fallback机制,为了防止推送机制失效导致配置不更新
客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。
3.客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
4.客户端会把从服务端获取到的配置在本地文件系统缓存一份
在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
5.应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知
欢迎扫描下面图片关注我的个人公众号,回复“资源”可以获取java核心知识整理和经典书籍