spring篇-(ssm自定义zookeeperPropertySource实现动态配置的加载)

Posted 小小白鸽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring篇-(ssm自定义zookeeperPropertySource实现动态配置的加载)相关的知识,希望对你有一定的参考价值。

写在前面

此博客是在:spring篇-(ssm自定义propertySource-加载classpath中application.yml|json|properties等配置文件实现)的基础上扩展开发,实现基于zookeeper的动态配置加载功能

添加CuratorFramework依赖

在pom.xml中添加操作zookeeper的框架CuratorFramework依赖,主要复制curator-framework相关依赖

        <curator-framework.version>5.1.0</curator-framework.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>${curator-framework.version}</version>
        </dependency>

创建ZookeeperPropertySourceLocator

创建zookeeperPropertySoruceLocator,实现从zookeeper中加载配置,解析并返回

package com.lhstack.custom.config.locator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lhstack
 * @date 2021/8/22
 * @class ZookeeperPropertySourceLocator
 * @since 1.8
 * 使用方式: yml配置如下
 * spring:
 *   application:
 *     name: 应用名称,默认application
 *     profile: 应用作用域,默认为空
 * zookeeper:
 *   connectString: localhost:2181,localhost:2191...
 *   namespace: xxx //可选,这样配置文件需要放在 /${namespace}${configContext}目录下面,如namespace为test contextContext为/config则目录为 /test/config
 *   sessionTimeout: session过期时间,单位为毫秒
 *   connectTimeout: 连接超时时间,单位为毫秒
 *   spector: 配置连接符 规则如下 ${spring.application.name:application}[-${spring.application.profile}]${zookeeper.spector}${zookeeper.extType}
 *   extType: 支持 json,yml,properties
 *   configContext: 设置目录,如果没设置namespace,则顶级目录为${contextContext},注意,需要添加/作为前缀
 *   defaultContext: 全局配置的application name 默认为application
 *   defaultEvtType: 支持json,yml,properties
 *   defaultSpector: 配置连接符 规则如下 ${defaultContext}[-${spring.application.profile}]${zookeeper.defaultSpector}${zookeeper.defaultEvtType}
 *   retry:
 *     retries: 连接失败最大重试次数
 *     baseSleepTimeMs: 基本重试延迟时间 单位毫秒
 *     maxSleepTimeMs: 最大重试延时时间 单位毫秒
 */
public class ZookeeperPropertySourceLocator implements PropertySourceLocator {

    /**
     * 操作zookeeper相关api的客户端
     */
    private CuratorFramework curatorFramework;

    private final List<String> contexts = new ArrayList<>();

    private String namespace = "";

    @Override
    public List<PropertySource<?>> locator() throws IOException {
        if (!this.namespace.isEmpty()) {
            List<String> cs = this.contexts.stream().map(item -> "/" + this.namespace + item)
                    .collect(Collectors.toList());
            System.out.println("load zookeeper config list " + cs);
        } else {
            System.out.println("load zookeeper config list " + this.contexts);
        }
        List<PropertySource<?>> list = new ArrayList<>();

        this.contexts.forEach(item -> {
            try {
                //检查path是否存在
                Stat stat = this.curatorFramework.checkExists().forPath(item);
                if (stat != null) {
                    byte[] bytes = this.curatorFramework.getData().forPath(item);
                    propertiesSource(list, bytes, item);
                    ymlSource(list, bytes, item);
                    jsonSource(list, bytes, item);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return list;
    }

    private void jsonSource(List<PropertySource<?>> list, byte[] bytes, String item) {
        if (item.endsWith(".json") && bytes.length > 0) {
            list.add(new MapPropertySource(item, PropertySourceUtils.parseJson(new ByteArrayInputStream(bytes))));
        }
    }

    private void ymlSource(List<PropertySource<?>> list, byte[] bytes, String item) {
        if (item.endsWith(".yml") && bytes.length > 0) {
            list.add(new MapPropertySource(item, PropertySourceUtils.parseYaml(new ByteArrayInputStream(bytes))));
        }
    }

    private void propertiesSource(List<PropertySource<?>> list, byte[] bytes, String item) throws IOException {
        if (item.endsWith(".properties") && bytes.length > 0) {
            Properties properties = new Properties();
            properties.load(new ByteArrayInputStream(bytes));
            list.add(new PropertiesPropertySource(item, properties));
        }
    }

    /**
     * 获取zookeeper相关环境配置,这里设计ApplicationPropertySourceLocator优先级最高,就是考虑到后续需要application.yml等配置文件中的内容实现动态配置加载
     *
     * @param env
     * @param beanFactory
     */
    @Override
    public void initEnvironment(Environment env, ConfigurableListableBeanFactory beanFactory) {
        if (env.containsProperty("zookeeper.connectString") && env.getProperty(
                "zookeeper.enable",
                Boolean.class,
                true
        )
        ) {
            CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
                    .connectString(env.getProperty("zookeeper.connectString"));
            //namespace
            if (env.containsProperty("zookeeper.namespace")) {
                builder.namespace(env.getProperty("zookeeper.namespace"));
                this.namespace = env.getProperty("zookeeper.namespace");
            }

            //session过期时间,30分钟
            builder.sessionTimeoutMs(env.getProperty("zookeeper.sessionTimeout", Integer.class, 180000));
            //连接超时
            builder.connectionTimeoutMs(env.getProperty("zookeeper.connectTimeout", Integer.class, 30000));
            //重试相关
            Integer retries = env.getProperty("zookeeper.retry.retries", Integer.class, 3);
            Integer retryBaseSleepTimeMs = env.getProperty("zookeeper.retry.baseSleepTimeMs", Integer.class, 10000);
            Integer retryMaxSleepTimeMs = env.getProperty("zookeeper.retry.maxSleepTimeMs", Integer.class, 30000);
            builder.retryPolicy(new BoundedExponentialBackoffRetry(retryBaseSleepTimeMs, retryMaxSleepTimeMs, retries));

            //连接符
            String spector = env.getProperty("zookeeper.spector", ".");
            //默认扩展类型 全局应用加载的默认配置
            String defaultExtType = env.getProperty("zookeeper.defaultEvtType", "yml");
            //扩展类型
            String extType = env.getProperty("zookeeper.extType", "yml");
            //默认应用名 全局应用加载的默认配置
            String defaultContext = env.getProperty("zookeeper.defaultContext", "application");
            //配置所在的目录
            String configContext = env.getProperty("zookeeper.configContext", "/config");
            String defaultSpector = env.getProperty("zookeeper.defaultSpector", ".");
            this.curatorFramework = builder.build();
            //应用名称,通过类似这种定义去加载指定的配置文件
            String applicationName = env.getProperty("spring.application.name", "application");
            //环境
            String profile = env.getProperty("spring.application.profile", "");
            this.curatorFramework.start();
            //考虑优先级问题,这里需要判断一下
            if (!profile.isEmpty()) {
                contexts.add(String.format("%s/%s-%s%s%s", configContext, applicationName, profile, spector, extType));
                contexts.add(String.format("%s/%s%s%s", configContext, applicationName, spector, extType));
                contexts.add(String.format("%s/%s-%s%s%s", configContext, defaultContext, profile, defaultSpector, defaultExtType));
            } else {
                contexts.add(String.format("%s/%s%s%s", configContext, applicationName, spector, extType));
            }
            contexts.add(String.format("%s/%s%s%s", configContext, defaultContext, defaultSpector, defaultExtType));
        }
    }
}

将其配置到容器中

在application.yml中添加zookeeper的配置

spring:
  application:
    name: custom-config #定义应用名称
    profile: dev #定义环境
zookeeper:
  connectString: 192.168.101.150:2181
  namespace: custom-config #定义namespace,用于一组微服务配置隔离

启动项目,并查看控制台

看到,控制台打印了配置加载路径,这个时候,在zookeper中的对应路径加上对应配置

分别在zookeeper中对应目录加上以下配置




修改TestController,读取这四个配置里面的配置内容

package com.lhstack.custom.config.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lhstack
 * @date 2021/8/22
 * @class TestController
 * @since 1.8
 */
@RestController
@RequestMapping
public class TestController {

    @Value("${spring.application.name}")
    private String name;

    @Value("${spring.application.custom-config}")
    private String value1;

    @Value("${spring.application.custom-config-dev}")
    private String value2;

    @Value("${spring.application}")
    private String value3;

    @Value("${spring.application-dev}")
    private String value4;

    @Autowired
    private TestEnv testEnv;
    
    @GetMapping("test")
    public String test1(){
        return value1 + ":" + value2 + ":" + value3 + ":" + value4;
    }

    @GetMapping
    public String test() {
        return name + "-" + testEnv.getValue();
    }

    public static class TestEnv {
        private String value;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}

启动并访问/test,看到配置是加载成功了

修改配置类型为properties,并测试加载

修改application.yml

spring:
  application:
    name: custom-config
    profile: dev
zookeeper:
  connectString: 192.168.101.150:2181
  namespace: custom-config
  extType: properties

在zookeeper中添加对应配置


启动项目并访问

修改配置类型为json,并测试加载

修改application.yml

spring:
  application:
    name: custom-config
    profile: dev
zookeeper:
  connectString: 192.168.101.150:2181
  namespace: custom-config
  extType: json

在zookeeper中添加对应配置


启动项目并访问

写在后面

这里基于zookeeper实现动态配置加载,也可以基于redis,mysql,或者其他中间件实现动态配置的功能,后续可以在此基础上添加配置动态更新的功能,实现项目在不重启的情况下,动态属性配置

以上是关于spring篇-(ssm自定义zookeeperPropertySource实现动态配置的加载)的主要内容,如果未能解决你的问题,请参考以下文章

SSM 框架集-01-详细介绍-入门问题篇

最基础的SSM框架整合篇

SSM框架(面试篇)

ssm开发经历汇总

SSM框架整合之练习篇

Spring Boot 揭秘与实战 应用监控篇 - 自定义监控端点