需求
不停止代码,动态修改springboot的enviorment的值。
原理:利用zookeeper的节点watch机制。
原理分析
1.Enviornment加载的时候会把application.properties中的文件,加载到enviorment–> propertySources–>propertySourcelist–>OriginTrackedMapProertySource中。
2.我们可以再程序启动的时候,使用@PostConstruct注解,去连接zookeeper,获取zookeeper的/config下所有的值,把这些值封装成一个ConCurrentHashMap,把这个conCourentHashMap注入到一个自定义的OriginTrackerMapPropertySource中。并且把这个OriginTrackerMapPropertySource添加到enviornment中,从而可以获取到值。
3.并且在之后根据zookeeper的watch机制,实现配置的动态修改。
实现
1.pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--提供了简化使用zookeeper更高级的API接口-->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<!-- 这个jar包的curator封装了很多zookeeper的操作功能-->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.11.0</version>
</dependency>
<!-- 这个jar包为了便于测试-->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-test -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<version>2.12.0</version>
<scope>test</scope>
</dependency>
</dependencies>
2.application.properties
server.port=8088
test.name=tom
3.App.java
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
4.CuratorUtil.java
@Component
public class CuratorUtil {
@Autowired
private Environment environment;
//上下文对象
@Autowired
ConfigurableApplicationContext applicationContext;
//第一个:后面是默认值。
@Value("${zookeeper.client:192.169.4.4:2181}")
private String connectStr;
//zookeeper的客户端
private static CuratorFramework client;
//你的application.properties放在zookeeper的哪个节点下
private static String path = "/config";
private static String zkPropertySourece = "z";
private static ConcurrentHashMap map = new ConcurrentHashMap();
//在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
@PostConstruct
public void init() {
client = CuratorFrameworkFactory.builder()
.connectString(connectStr)
.sessionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
try {
//1.检查在zookeeper中我们的配置节点,这个节点存在不
Stat stat = client.checkExists().forPath(path);
if (stat == null) {
//如果不存在,就创建一个持久节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
.forPath(path, "zookeeper config".getBytes());
} else {
//2.有了/config节点后,在App程序应用开始的时候,要加载/config节点数据到spring的enviornment中
addChildToSpringProperties(client, path);
}
//3.监控节点的变化,节点的新增,修改,删除都会被监控到,然后动态修改PropertySource中的map的数据。
childNodeCache(client, path);
} catch (Exception e) {
e.printStackTrace();
}
}
private void childNodeCache(CuratorFramework client, String path) {
try {
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, true);
pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
//添加一个监听。PathChildrenCacheListener一次父节点注册,监听每次子节点操作,不监听自身和查询。
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("添加了节点");
addEnv(event.getData(), client);
break;
case CHILD_UPDATED:
System.out.println("修改了节点");
addEnv(event.getData(), client);
break;
case CHILD_REMOVED:
System.out.println("删除了节点");
delEnv(event.getData(), client);
break;
default:
break;
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void delEnv(ChildData data, CuratorFramework client) {
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
PropertySource<?> zkProperty = propertySources.get(zkPropertySourece);
ConcurrentHashMap zkMap = (ConcurrentHashMap) zkProperty.getSource();
String childpath = data.getPath();
zkMap.remove(childpath.substring(path.length() + 1));
}
//修改PropertySource对象
private void addEnv(ChildData data, CuratorFramework client) {
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
PropertySource<?> zkProperty = propertySources.get(zkPropertySourece);
ConcurrentHashMap zkMap = (ConcurrentHashMap) zkProperty.getSource();
String childpath = data.getPath();
String content = null;
try {
content = new String(client.getData().forPath(childpath));
} catch (Exception e) {
e.printStackTrace();
}
//childPath = /config/username
zkMap.put(childpath.substring(path.length() + 1), content);
}
//把zookeeper中的配置属性值也封装成一个PropertySource对象,然后把这个对象设置到Enviornment中
private void addChildToSpringProperties(CuratorFramework client, String path) {
//1.判断enviornment上下文中,是否已经存在了我们自定义的PropertySource
if (!checkExistesProperty()) {
//如果不存在(第一次启动的时候不存在,后面动态修改的时候都应该已经存在了),创建一个zkPropertySource
createZkPropertySource();
}
//2.加载zk /config下节点的所有的数据,放在自定义的PropertySource的map中。
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
PropertySource<?> zkProperty = propertySources.get(zkPropertySourece);
ConcurrentHashMap zkMap = (ConcurrentHashMap) zkProperty.getSource();
List<String> strings = null;
try {
//获取/config跟节点下的所有的 节点路径。
strings = client.getChildren().forPath(path);
for (String string : strings) {
zkMap.put(string, new String(client.getData().forPath(path + "/" + string)));
}
} catch (Exception e) {
e.printStackTrace();
}
}
//创建自定义的zkPropertySource
private void createZkPropertySource() {
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
//OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}
//参数1: 自定义的PropertySource的名称 参数2:这个自定义的PropertySource里面需要存储数据的集合。
OriginTrackedMapPropertySource mapPropertySource = new OriginTrackedMapPropertySource(zkPropertySourece, map);
propertySources.addLast(mapPropertySource);
}
private boolean checkExistesProperty() {
//Enviorment中所有的Propertyes
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
//每个PropertySource都有唯一的一个名字,我们设置的名字是zkPropertySourece=z
if (propertySource.getName().equals(zkPropertySourece)) {
return true;
}
}
return false;
}
}
5.TestController.java
@RestController
public class TestController {
@Autowired
private Environment environment;
@GetMapping("test")
public String get(){
String username = environment.getProperty("username1");
String password = environment.getProperty("password2");
return "Enviorment: username=" + username + ",password=" + password;
}
}
测试
1.启动zookeeper
1.启动zookeeper
2.cd bin
3.zkCli.sh
4.create /config
5.create /config/username1
6.create /config/password2
7.set /config/username1 tom
8.set /config/password2 123
2.启动springboot程序
App.java idea中run
3.浏览器访问
localhost:8088/test
获取到数据: Enviorment: username=tom,password=123
4.修改zookeeper节点数据
1.cd bin
2.zkCli.sh
3.set /config/username1 nihao
4.set /config/password2 good
5.浏览器访问(程序不需要重启)
localhost:8088/test
Enviorment: username=nihaoa,password=good