spring篇-(ssm自定义zookeeperPropertySource实现动态配置的加载)
Posted 小小白鸽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring篇-(ssm自定义zookeeperPropertySource实现动态配置的加载)相关的知识,希望对你有一定的参考价值。
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实现动态配置的加载)的主要内容,如果未能解决你的问题,请参考以下文章