分布式动态配置参数,相信是很多公司都要做的,改一处配置,各个地方都可以用,最重要的是大多数情况下都希望能够再应用运行时修改配置参数而无需重启应用:
在这里,作者看了好多解决方案,但都不理想,所以自己来实现,主要如下:
@Autowired
private PropertyUtils propertyUtils;
/**
* 自动从zookeeper获取配置信息并重写本地配置文件
*
*/
@Bean
public String changeEnvironment(ApplicationContext applicationContext) {
//支持Environment获取 修改容器里面StandardServletEnvironment
StandardEnvironment standardEnvironment = applicationContext.getBean(StandardEnvironment.class);
//根据本地loadPropFromRemote的参数是否为true来确定是否要从远程zookeeper加载配置数据
if (StringUtils.isNotEmpty(standardEnvironment.getProperty("loadPropFromRemote")) && "true".equals(standardEnvironment.getProperty("loadPropFromRemote"))) {
Properties properties = propertyUtils.loadProperties(standardEnvironment);
standardEnvironment.getPropertySources().replace("applicationConfig: [classpath:/application.properties]", new PropertiesPropertySource("applicationConfig: [classpath:/application.properties]", properties));
}else {
Properties properties = (Properties)standardEnvironment.getPropertySources().get("applicationConfig: [classpath:/application.properties]").getSource();
propertyUtils.cacheProperties(properties);
}
return null;
}
package com.wos.utils;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.logging.log4j.LogManager;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* 从zookeeper获取配置信息,监听zookeeper
* 缓存配置到CACHE_PROPERTIES 供程序使用
*/
@Component
public class PropertyUtils {
private static org.apache.logging.log4j.Logger log=LogManager.getLogger(PropertyUtils.class);
private static NodeCache cache;
private Properties properties;
private static final Map CACHE_PROPERTIES = new ConcurrentHashMap<>();
private static final String ENCODING = "UTF-8";
public Properties loadProperties(StandardEnvironment standardEnvironment){
Map props=new HashMap<>(3);
props.put("zookeeper.address",standardEnvironment.getProperty("zookeeper.address"));
props.put("app.name",standardEnvironment.getProperty("app.name"));
props.put("app.version",standardEnvironment.getProperty("app.version"));
loadPropertiesFromZookeeper(props);
return properties;
}
private void loadPropertiesFromZookeeper(Map props) {
log.info("开始从zookeeper获取配置信息");
String zookeeperAddress = props.get("zookeeper.address");
String appName = props.get("app.name");
String appVersion = props.get("app.version");
if (StringUtils.isBlank(zookeeperAddress) || StringUtils.isBlank(appName)
|| StringUtils.isBlank(appVersion)) {
throw new RuntimeException("配置项[zookeeper.address, app.name, app.version]不能为空");
}
try {
CuratorFramework client = ZookeeperUtil.getClient(zookeeperAddress);
String path = "/configuration/" + appName + "/" + appVersion + "/config";
byte[] data = client.getData().forPath(path);
properties = loadRemoteProperties(data);
cacheProperties(properties);
log.info("获取远程配置信息成功");
cache = new NodeCache(client, path);
cache.start(true);
cache.getListenable().addListener(() -> {
ChildData data1 = cache.getCurrentData();
log.info("远程配置信息变更,重新加载远程配置信息");
properties = loadRemoteProperties(data1.getData());
cacheProperties(properties);
});
} catch (Exception e) {
throw new RuntimeException("从zookeeper获取配置信息失败", e);
}
log.info("配置文件初始化完成");
}
private Properties loadRemoteProperties(byte[] data) throws IOException {
String config = new String(data, ENCODING);
Properties p = new Properties();
p.load(new StringReader(config));
return p;
}
/**
* 缓存配置信息
*
* @param p 具体配置
*/
public void cacheProperties(Properties p) {
for (Object key : p.keySet()) {
if (key instanceof String) {
Object value = p.get(key);
if (value instanceof String) {
PropertyUtils.set((String) key, (String) value);
}
}
}
}
public static String get(String key) {
return CACHE_PROPERTIES.get(key);
}
protected static Map getProperties() {
return CACHE_PROPERTIES;
}
private static void set(String key, String value) {
CACHE_PROPERTIES.put(key, value);
}
}
package com.wos.utils;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
/**
* zookeeper工具类
*/
public class ZookeeperUtil {
private static CuratorFramework client;
public static final String TYPE_CONFIGURATION = "configuration";
@Value("$(zookeeper.address)")
public static String ADDRESS;
/**
* 缓存client
* @param c
*/
protected synchronized static void cache(CuratorFramework c){
client = c;
}
public static CuratorFramework getClient(){
if(client == null){
return getClient(ADDRESS);
}
return client;
}
public static synchronized CuratorFramework getClient(String address){
if(client == null){
if(StringUtils.isEmpty(address)){
throw new RuntimeException("zookeeper地址不能为空");
}
//解决 zookeeper地址解析异常
address = address.replace("zookeeper://", "");
address = address.replace("?backup=", ",");
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
client = CuratorFrameworkFactory.newClient(address, retryPolicy);
client.start();
}
return client;
}
public static String getStringData(Stat stat, String path, String encode){
try {
byte[] data = getClient().getData().storingStatIn(stat).forPath(path);
return new String(data, encode);
}catch(NoNodeException noNode){
return null;
}
catch (Exception e) {
throw new RuntimeException("获取信息失败", e);
}
}
public static String getStringData(String path, String encode){
try {
byte[] data = getClient().getData().forPath(path);
return new String(data, encode);
}catch(NoNodeException noNode){
return null;
}
catch (Exception e) {
throw new RuntimeException("获取信息失败", e);
}
}
public static void saveOrUpdate(CuratorFramework client, String path, byte[] data) throws Exception{
Stat stat = client.checkExists().forPath(path);
if(stat != null){
client.setData().forPath(path, data);
}else{
client.create().creatingParentsIfNeeded().forPath(path, data);
}
}
}
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.5version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>2.10.0version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.3version>
dependency>