前言
配置中心的一些想法:
在 Zookeeper 的节点中保存配置。
然后通过SpringBoot 提供的配置扩展接口 EnvironmentPostProcessor 从Zookeeper 中获取初始化需要的配置。
后续监听 Zookeeper 节点的变化,对配置进行更新。
zkClient的依赖如下:
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.2</version>
</dependency>
自定义的配置Bean:TestProperty:
@Component
public class TestProperty implements Serializable {
@Value("${name}")
private String name;
@Value("${age}")
private String age;
// 省略getter setter方法
}
操作的接口:EnvironmentPostProcessor
这是SpringBoot的一个扩展接口。我们可以利用此接口在ApplicationContext创建之前对配置进行一些处理。
首先自定义 MyEnvironmentPostProcessor 去实现扩展接口:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 配置ZK
ZkClient zkClient = new ZkClient("192.168.62.129:2181", 10000, 10000, new MyZkSerializer());
String name = zkClient.readData("/test/name");
String age = zkClient.readData("/test/age",);
System.out.println("第一次获取的name:" + name);
System.out.println("第一次获取的age:" + age);
// propertySources是所有的配置文件!是一个list集合
MutablePropertySources propertySources = environment.getPropertySources();
Iterator<PropertySource<?>> iterator = propertySources.iterator();
// 遍历当前所有的配置文件
while (iterator.hasNext()) {
System.out.println(iterator.next().toString());
}
Map<String, Object> map = new HashMap<>(8);
map.put("name", name);
map.put("age", age);
// 把ZK中获取的配置进行添加
MapPropertySource mapPropertySource = new MapPropertySource("TestProperty", map);
propertySources.addLast(mapPropertySource);
}
}
对了别忘记了 spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.wys.combineduse.Environment.MyEnvironmentPostProcessor
这里有一个坑,那就是 ZkClient 在初始化的时候需要去配置序列化类。我在别人的文章中看见默认的 SerializableSerializer() 也可以。但是我在运行的时候是一直报 org.I0Itec.zkclient.exception.ZkMarshallingError: java.io.EOFException
后面自己去实现了序列化的类后就好了:实现是百度的!
public class MyZkSerializer implements ZkSerializer {
@Override
public byte[] serialize(Object o) throws ZkMarshallingError {
return String.valueOf(o).getBytes(Charsets.UTF_8);
}
@Override
public Object deserialize(byte[] bytes) throws ZkMarshallingError {
return new String(bytes, Charsets.UTF_8);
}
}
接下来就是对ZK节点变化的监听
一开始我以为直接在 MyEnvironmentPostProcessor 中添加如下代码:
zkClient.subscribeDataChanges(path,new IZkDataListener() {
@Override
public void handleDataChange(java.lang.String s, Object o) throws Exception {
testProperty.setName(o.toString());
System.out.println("testProperty.setName(o.toString()):" + o.toString());
}
@Override
public void handleDataDeleted(java.lang.String s) throws Exception {
}
});
现实是残酷,我们需要开一个线程去进行监听。我采用了从 bean 构造函数中执行,
在上下文关闭时清理线程。这样的一种方式。来源:https://stackoverflow.com/questions/39737013/spring-boot-best-way-to-start-a-background-thread-on-deployment
我参考实现如下:
@Component
public class MyZkClientEventSubscriber implements DisposableBean, Runnable {
@Resource
TestProperty testProperty;
private String path;
private Thread thread;
private static final ZkClient zkClient = new ZkClient("192.168.62.129:2181",10000,10000,new MyZkSerializer());
MyZkClientEventSubscriber() {
this.path = "/test/name";
this.thread = new Thread(this, "my_zk_client_holder_thread");
this.thread.start();
}
@Override
public void run() {
zkClient.subscribeDataChanges(path, new IZkDataListener() {
@Override
public void handleDataChange(java.lang.String s, Object o) throws Exception {
testProperty.setName(o.toString());
System.out.println("testProperty的name属性变成了:" + o.toString());
}
@Override
public void handleDataDeleted(java.lang.String s) throws Exception {
}
});
}
@Override
public void destroy() throws Exception {
}
}
这样后,我们在ZK中对 /test/name 节点进行修改时,SpringBoot中对应的 TestProperty 的属性值也发生了变化。
后续想法
是否可以在Zookeeper中存配置的版本信息,然后再Mysql中保存具体的配置。