把分布式项目的比如数据库访问地址,用户名等,统一放到Zookeeper中进行管理,项目只需指定zookeeper地址即可。
一、先添加maven依赖:
<!-- ZooKeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.4.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.4.2</version>
<scope>provided</scope>
</dependency>
<!-- Zookeeper -->
然后只需两个类即可完成目标:
二、类ZooKeeperService.java
package cn.com.easy.zookeeper;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.collections.CollectionUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
/**
* zookeeper服务类,用于创建操作zookeeper的对象
*
* @author nibili 2015年5月7日
*
*/
public class ZooKeeperService {
private Logger logger = LoggerFactory.getLogger(ZooKeeperService.class);
public static final int MAX_RETRIES = 3000;
public static final int BASE_SLEEP_TIMEMS = 3000;
/** zookeeper服务器列表 */
private String zookeeperServers = "";
/** zookeeper客户端操纵对象 */
private CuratorFramework client;
/** 监听器集合(一键多值数据结构) */
private Multimap<IZookeeperWatch, Object> watchesMap = ArrayListMultimap.create();
public ZooKeeperService(String zookeeperServers) {
this.zookeeperServers = zookeeperServers;
RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIMEMS, MAX_RETRIES);
this.client = CuratorFrameworkFactory.builder().connectString(this.zookeeperServers).retryPolicy(retryPolicy).build();
client.start();
}
/**
* 取消监听,
*
* @param zookeeperWatch
* 注册监听时的对象
* @auth nibili 2015年5月8日
*/
public void removeNodeWatch(IZookeeperWatch zookeeperWatch) {
if (zookeeperWatch == null) {
logger.info("称除节点监听,监听器对象不能为空!");
return;
}
Collection<Object> values = watchesMap.get(zookeeperWatch);
if (CollectionUtils.isNotEmpty(values) == true) {
// 移除监听器
NodeCache cache = null;
NodeCacheListener nodeCacheListener = null;
Iterator<Object> it = values.iterator();
for (int i = 0; it.hasNext() && i < 2; i++) {
if (i == 0) {
cache = (NodeCache) it.next();
} else if (i == 1) {
nodeCacheListener = (NodeCacheListener) it.next();
} else {
break;
}
}
if (cache != null && nodeCacheListener != null) {
cache.getListenable().removeListener(nodeCacheListener);
}
} else {
logger.info("没有找到对应的监听器!");
return;
}
}
/**
* 监听节点变化
*
* @param zookeeperWatch
* @throws Exception
* @auth nibili 2015年5月8日
*/
public void addNodeWatch(final IZookeeperWatch zookeeperWatch) throws Exception {
// 是否是每一次触发
final AtomicBoolean isFirst = new AtomicBoolean(true);
final NodeCache cache = new NodeCache(this.client, zookeeperWatch.getWatchPath());
cache.start();
NodeCacheListener nodeCacheListener = new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
// 节点数据
String data = new String(cache.getCurrentData().getData(), "UTF-8");
if (isFirst.get() == true) {
isFirst.set(false);
logger.debug("NodeCache loaded, data is: " + data);
zookeeperWatch.handLoad(data);
} else {
logger.debug("NodeCache changed, data is: " + data);
zookeeperWatch.handChange(data);
}
}
};
cache.getListenable().addListener(nodeCacheListener);
watchesMap.put(zookeeperWatch, cache);
watchesMap.put(zookeeperWatch, nodeCacheListener);
}
/**
* 断开连接
*
* @auth nibili 2015年5月7日
*/
public void close() {
client.close();
}
/**
* 获取zookeeper操纵对象
*
* @param servers
* @return
* @auth nibili 2015年5月7日
*/
public CuratorFramework getClient() {
return client;
}
/**
* 获取服务器地址
*
* @return
* @throws Exception
* @auth nibili 2015年5月7日
*/
public String getServers() {
return this.zookeeperServers;
}
/**
* 设置节点值
*
* @param path
* @param data
* @auth nibili 2015年5月8日
*/
public void setPathValue(String path, String data) {
try {
logger.debug("设置结点值,path:" + path + ",data:" + data);
this.client.setData().forPath(path, data.getBytes("UTF-8"));
} catch (Exception e) {
logger.error("设置zookeeper节点值异常,path:" + path + ",data" + data, e);
}
}
/**
* 获取节点值
*
* @param path
* @return
* @throws Exception
* @auth nibili 2015年5月7日
*/
public byte[] getPathValue(String path) throws Exception {
if (!exists(this.client, path)) {
throw new RuntimeException("Path " + path + " does not exists.");
}
return client.getData().forPath(path);
}
/**
* 节点是否存在
*
* @param client
* @param path
* @return
* @throws Exception
* @auth nibili 2015年5月7日
*/
private boolean exists(CuratorFramework client, String path) throws Exception {
Stat stat = client.checkExists().forPath(path);
return !(stat == null);
}
/**
* 获取子节点
*
* @param path
* @return
* @throws Exception
* @auth nibili 2015年5月7日
*/
public List<String> getSubPaths(String path) throws Exception {
return client.getChildren().forPath(path);
}
}
三、类ZooKeeperPropertyPlaceholderConfigurer.java
package cn.com.easy.zookeeper;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import com.google.common.collect.Maps;
/**
* zookeeper配置<br>
* 继承spring加载上下文属性文件的类<br>
* 如果properties中的属性名与zookeeper中的一样,那么参数值将会被zookeeper上的值覆盖。<br>
* properties文件配置两个参数:<br>
* zk.servers=192.168.1.156:2181,192.168.1.120:2181 <br>
* #zk.config.root.path defaut value id "/cn/com/easy/config",u could delete the
* set<br>
* #可选,默认为/cn/com/easy/config<br>
* zk.config.root.path=/cn/com/easy/config<br>
*
*
* @author nibili 2015年5月7日
*
*/
public class ZooKeeperPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private Logger logger = LoggerFactory.getLogger(ZooKeeperPropertyPlaceholderConfigurer.class);
/** zookeeper服务器地址 的properties参数名,在properties文件中设置 */
private final String ZOOKEEPER_SERVERS_PRO = "zk.servers";
/** 所有配置所在zookeeper的根节点的 的properties参数名,在properties文件中设置 */
private final String ZOOKEEPER_CONFIG_ROOT_PATH_PRO = "zk.config.root.path";
/** 项目配置数据的根节点 */
private final String CONFIG_ROOT_PATH = "/cn/com/easy/config";
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
super.processProperties(beanFactoryToProcess, props);
try {
// zookeeper服务器
String zookeeperServers = props.getProperty(ZOOKEEPER_SERVERS_PRO);
// 配置的根节点
String configRootPath = props.getProperty(ZOOKEEPER_CONFIG_ROOT_PATH_PRO);
if (StringUtils.isBlank(configRootPath) == true) {
configRootPath = CONFIG_ROOT_PATH;
}
Map<String, String> customProperties = this.getConfigurationInZookeeper(zookeeperServers, configRootPath);
props.putAll(customProperties);
logger.debug(props.toString());
} catch (Exception e) {
logger.error("从Zookeeper获取配置异常!" + e.getMessage(), e);
}
}
/**
* 获取zookeeper中的配置数据
*
* @param zookeeperServers
* @param configRootPath
* @return
* @throws Exception
* @auth nibili 2015年5月7日
*/
private Map<String, String> getConfigurationInZookeeper(String zookeeperServers, String configRootPath) throws Exception {
// 服务器地址不能为空
if (StringUtils.isBlank(zookeeperServers) == true) {
throw new Exception("Zookeeper服务器地址不能为空!");
}
// 属性名,属性值对应的map
Map<String, String> propertiesInZkMap = Maps.newHashMap();
//
ZooKeeperService zooKeeperService = new ZooKeeperService(zookeeperServers);
// 获取所有子节点
List<String> paths = zooKeeperService.getSubPaths(configRootPath);
// 遍历所有子节点,以及节点值
if (CollectionUtils.isNotEmpty(paths) == true) {
// 遍历有子节点
for (String path : paths) {
byte[] data = zooKeeperService.getPathValue(configRootPath + "/" + path);
if (data != null) {
String value = new String(data, "UTF-8");
if (StringUtils.isNotBlank(value) == true) {
propertiesInZkMap.put(path, value);
}
}
}
}
zooKeeperService.close();
return propertiesInZkMap;
}
}
四、修改properties文件
zk.servers=192.168.1.156:2181,192.168.1.120:2181
#zk.config.root.path defaut value id "/cn/com/easy/config",u could delete the set
zk.config.root.path=/summall/conf
当然文件中可添加,自己项目要使用的一些属性,也可以生交效,但是如果zookeeper中存在某个属性,那么会被zookeeper中存储的值所替换。
五、修改applicationContext.xml文件
<bean class="cn.com.easy.zookeeper.ZooKeeperPropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:applicationContext-zookeeper-config-demo.properties</value>
</list>
</property>
</bean>
六、使用zookeeper客户端添加节点,例:
./zkCli.sh
[zk: localhost:2181(CONNECTED) 0] create /cn ""
Created /cn
[zk: localhost:2181(CONNECTED) 1] create /cn/com ""
Created /cn/com
[zk: localhost:2181(CONNECTED) 4] create /cn/com/easy ""
Created /cn/com/easy
[zk: localhost:2181(CONNECTED) 5] create /cn/com/easy/config ""
Created /cn/com/easy/config
[zk: localhost:2181(CONNECTED) 15] create /cn/com/easy/config/jdbc.url "jdbc:mysql://localhost:3306/wac"
Created /cn/com/easy/config/jdbc.url
[zk: localhost:2181(CONNECTED) 16] create /cn/com/easy/config/jdbc.username "root"
Created /cn/com/easy/config/jdbc.username
[zk: localhost:2181(CONNECTED) 17] create /cn/com/easy/config/jdbc.password "123456"
Created /cn/com/easy/config/jdbc.password
这样配置就完成了,已经可以把zookeeper中的统一配置项加载到spring上下文了!