zookeeper动态修改spring中的Enviornment对象中的配置

需求

不停止代码,动态修改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
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值