一、背景

通过上一篇日志,我们已经将一个单机版的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类型的配置文件)

java apollo 配置变更通知 java获取apollo配置数据demo_Apollo

创建好对应的namespace之后,页面会提示需要发布对应的key-value配置项,如果为空的话会导致Apollo client所在的工程报空指针异常!

java apollo 配置变更通知 java获取apollo配置数据demo_spring_02

3. 工程如何引入在Apollo中的各配置项?

namespace配置文件定位
apollo利用appid、cluster_name、namespace来最终定位一个配置文件,
其中 appid和cluster_name都是作为系统参数在应用启动之前就设置好了,而namespace作为客户端程序初始化的一个参数设定!

appid通过在项目的META-INF下面的app.prperties文件中指定,例:app.id=yanghe66966,其中yanghe66966为apollo中创建的一个appid,如何创建官网上面写的很详细,我这里就不罗嗦了

java apollo 配置变更通知 java获取apollo配置数据demo_java_03

cluster_name通过jvm运行参数指定:“-Dapollo.cluster=default”

  • Apollo首先会尝试从用户指定的cluster名称的集群加载配置
  • 如果没找到,会从默认的集群(default)加载

java apollo 配置变更通知 java获取apollo配置数据demo_java_04

java apollo 配置变更通知 java获取apollo配置数据demo_java apollo 配置变更通知_05

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对象,目的是将指定的配置文件封装到指定对象,便于整个工程调用

java apollo 配置变更通知 java获取apollo配置数据demo_配置文件_06

如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方法,比便可获取到相应的配置了

java apollo 配置变更通知 java获取apollo配置数据demo_java apollo 配置变更通知_07

三、总结

1.apollo的使用方式有好几种,我这里只演示了使用spring方式引入apollo client的例子,不过大家也开出来了,其使用还是比较简单的,而且如果你原来项目中本来就是使用@value()注解形式引入配置文件的话,那么对已有代码的改动是很小的!我已经将我一个项目配置切换到apollo了。

2.要想在实际项目中使用的话,必须要考虑配置文件的动态配置策略,尤其是形如:zookeeper、消息中间件、分布式缓存等,一旦他的配置信息有变动,如何在不重启应用的情况下,快速切换对应的client,这个问题是必须要解决的,这里给大家提供一个思路(因为我就是这样子做的):譬如消息中间件,项目启动的时候需要将通过javaAPI实例化的client保存起来,然后等到监控到其对应的配置信息发生变动时,直接关闭原client,关闭之后利用新的配置信息重新实例化一个newclient对象!