一、背景
通过上一篇日志,我们已经将一个单机版的apollo server端搭建起来了,并且可以通过官方提供的demo实现最基本的配置文件的读取。接下来我主要通过一个实际的spring项目来演示如何利用apollo的java客户端实现项目的配置文件动态更新。
apollo的Java客户端可以通过纯java API,spring注解,springboot注解等形式绑定到我们的项目中,我项目使用的spring4.x搭建的,因此我使用spring注解方式将apollo客户端添加到我的工程中来。
二、实战
1.先引入apollo-client的maven依赖
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.1.0</version>
</dependency>
2.创建自定义namespace,其中application是默认的namespace(一个namespace可以理解成一个properties文件的名称,因为我只用到了properties类型的配置文件)
创建好对应的namespace之后,页面会提示需要发布对应的key-value配置项,如果为空的话会导致Apollo client所在的工程报空指针异常!
3. 工程如何引入在Apollo中的各配置项?
namespace配置文件定位
apollo利用appid、cluster_name、namespace来最终定位一个配置文件,
其中 appid和cluster_name都是作为系统参数在应用启动之前就设置好了,而namespace作为客户端程序初始化的一个参数设定!
appid通过在项目的META-INF下面的app.prperties文件中指定,例:app.id=yanghe66966,其中yanghe66966为apollo中创建的一个appid,如何创建官网上面写的很详细,我这里就不罗嗦了
cluster_name通过jvm运行参数指定:“-Dapollo.cluster=default”
- Apollo首先会尝试从用户指定的cluster名称的集群加载配置
- 如果没找到,会从默认的集群(
default
)加载
namespace的获取,在javaconfig中,使用@EnableApolloConfig()注解,加载指定的namespace
@Configuration()
@EnableApolloConfig(value = {"application","TEST2.RegistryCenter","TEST2.MessageMiddleware","TEST2.DistributedCaching"}, order = 10)//appllo
public class BasicConfig {
{
/**
* 集群和环境设置未起作用
*/
System.setProperty("apollo.meta", "http://192.168.219.130:8080");
System.setProperty("idc", "DEV");//环境
}
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
创建与四个namespace一一对应的四个本地POJO对象,目的是将指定的配置文件封装到指定对象,便于整个工程调用
如RegistryCenterProperties类的代码如下,@component注解表示当前类的生命周期由spring负责,@value()注解就是使用的spring自带的注解
/**
* 注册中心配置项
* @Description:
* @author yangcheng
* @date: 2019年6月23日
*/
@Component
public class RegistryCenterProperties {
Logger logger = LoggerFactory.getLogger(RegistryCenterProperties.class);
private final String curNamespace = "TEST2.RegistryCenter";
/**
* 是否开启集群模式
*/
@Value("${CLUSTER_OPEN}")//官方建议给出默认值,如@Value("${CLUSTER_OPEN:ON}")--默认值为ON
private String clusteropen;
/**
* 当前注册中心name
*/
@Value("${REGISTER_CENTER_NAME}")
private String curRegisterCenterName;
/**
* zookkeeper相关的配置
*/
@Value("${ZK_ADDR}")
private String zkAddr;
@Value("${ZK_SESSION_OUTTIME}")
private int zkSessionOutTime;
//初次重试时间
@Value("${ZK_BASE_SLEEP_TIME_MS}")
private int zkBaseSleepTimeMs;
//最大重试次数
@Value("${ZK_MAX_RETRIES}")
private int zkMaxRetries;
/**
* 监听namespace为TEST2.RegistryCenter的变化
* @param changeEvent
*/
@ApolloConfigChangeListener(curNamespace)
private void anotherOnChange(ConfigChangeEvent changeEvent) {
if(curNamespace.equals(changeEvent.getNamespace())){
Set<String> propertySet = changeEvent.changedKeys();
for (String property : propertySet) {
String newval = changeEvent.getChange(property).getNewValue();
// System.out.println("old = " + clusteropen +";new = "+newval);
/**
* 亲测 变化后 properties对象通过Value注解赋值的属性会自动变动到最新值,不过捕获事件到属性值
* 变化有100毫秒左右的延迟!
* 可以在app.properties中增加“apollo.autoUpdateInjectedSpringProperties=false”配置
* 关闭Value注解关联的属性自动刷新
*/
try {
Thread.sleep(500);
} catch (InterruptedException e) {
logger.error(e.getMessage());
}finally {
try {
//do something
} catch (InterruptedException e) {
logger.error("注册中心相关配置参数设定失败"+e.getMessage());
}
}
}
}
}
public String getClusteropen() {
return clusteropen;
}
public String getCurRegisterCenterName() {
return curRegisterCenterName;
}
public String getZkAddr() {
return zkAddr;
}
public int getZkSessionOutTime() {
return zkSessionOutTime;
}
public int getZkBaseSleepTimeMs() {
return zkBaseSleepTimeMs;
}
public int getZkMaxRetries() {
return zkMaxRetries;
}
@Override
public String toString() {
return "RegistryCenterProperties [clusteropen=" + clusteropen + ", curRegisterCenterName="
+ curRegisterCenterName + ", zkAddr=" + zkAddr + ", zkSessionOutTime=" + zkSessionOutTime
+ ", zkBaseSleepTimeMs=" + zkBaseSleepTimeMs + ", zkMaxRetries=" + zkMaxRetries + "]";
}
}
需要使用对应配置时,只需要将RegistryCenterProperties注入到对应类中而后通过RegistryCenterProperties的get方法,比便可获取到相应的配置了
三、总结
1.apollo的使用方式有好几种,我这里只演示了使用spring方式引入apollo client的例子,不过大家也开出来了,其使用还是比较简单的,而且如果你原来项目中本来就是使用@value()注解形式引入配置文件的话,那么对已有代码的改动是很小的!我已经将我一个项目配置切换到apollo了。
2.要想在实际项目中使用的话,必须要考虑配置文件的动态配置策略,尤其是形如:zookeeper、消息中间件、分布式缓存等,一旦他的配置信息有变动,如何在不重启应用的情况下,快速切换对应的client,这个问题是必须要解决的,这里给大家提供一个思路(因为我就是这样子做的):譬如消息中间件,项目启动的时候需要将通过javaAPI实例化的client保存起来,然后等到监控到其对应的配置信息发生变动时,直接关闭原client,关闭之后利用新的配置信息重新实例化一个newclient对象!