前言:
看过JbossCache的开发手册,发现在JbossCache与AppServer的集成章节对JNDI方式的集成没有明确说明,在Jboss App Server 下,你可以使用MBean方式管理JbossCache,更多情况下,你可能需要写一个Factory类,通过API方法来启用JbossCache了。
在企业的应用中,尤其是SOA结构的系统中,在一个应用服务器上,同时运行多个应用的场景是经常遇到的,JbossCache作为群集缓存的实现,我们希望是对一个应用服务器上的多应用,分享一个JBossCache的实例,而不是为每个应用起一个单独的。
1.Tomcat JNDI 实现
Tomcat JNDI估计大家都接触过,最常用的莫过于数据库的DataSource了。在Tomcat服务器启动后,它的JNDI会呈现Read Only模式,无法通过API方式往上发布资源。因此我们需要实现一个可以通过配置声明的JNDI JbossCache服务。
声明一个Cache逻辑相关的接口 - ClusterCache
ClusterCache接口是对JbossCache的Cache接口的简化封装。代码如下:
package org.wltea.cache;
import java.util.Map;
import java.util.Set;
import org.jboss.cache.Node;
public interface ClusterCache {
/**
* 打开cache的批处理事务
*/
public void beginBatchTx();
/**
* 提交cache的批处理事务
*/
public void commitBatchTx();
/**
* 回滚cache的批处理事务
*/
public void rollbackBatchTx();
/**
* 在指定的结点上缓存一个key-value型数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param key 缓存对象的键
* @param value 缓存对象
*/
public void cacheData(String nodePath , String key , Object value);
/**
* 在指定的结点上缓存整个Map数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param dataMap 缓存对象的Map
*/
public void cacheDataMap(String nodePath , Map<String , Object> dataMap);
/**
* 获取指定结点上缓存的数据对象
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param key 缓存对象的键
* @return Object 返回缓存对象
*/
public Object getCacheData(String nodePath , String key);
/**
* 获取指定结点上缓存的数据集
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @return Map 返回缓存的Map数据集
*/
public Map<String , Object> getCacheDataMap(String nodePath);
/**
* 清除指定结点上的缓存数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
*/
public void clearData(String nodePath);
/**
* 取得指定结点上的缓存数据的记录数
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
*/
public int getDataSize(String nodePath);
/**
* 获取指定结点上的子结点集合
* @param nodePath
* @return Set<Node<String,Object>> , 返回指定结点上的子结点集合
*/
public Set<Node<String,Object>> getChildren(String nodePath);
/**
* 移出指定结点上的单个数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param key 缓存对象的键
* @return Object 返回缓存对象
*/
public Object removeData(String nodePath , String key);
/**
* 移出整个的指定结点
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @return boolean true:成功移除结点 ; false:没有找到指定结点
*/
public boolean removeNode(String nodePath);
}
将ClusterCache接口实现成可发布的JNDI 引用 -
任何资源要发布成JNDI,就必须实现javax.naming.Referenceable接口,该接口只有一个方法public Reference getReference() ;它返回一个javax.naming.Reference类型的对象。JNDI的SPI端需要这个Reference 对象提供足够的信息来初始化JNDI资源实例并提供给使用者。
对于ClusterCache业务接口的Referenceable实现如下:
/**
*
*/
package org.wltea.cache;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheFactory;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
/**
* 群集缓存JNDI实现
* @author 林良益
*
*/
public class JNDIClusterCache implements Referenceable , ClusterCache{
//配置文件路径
private static String CONFIG_FILE_LOCATION = "jbosscache.cfg.xml";
//Jboss Cache 工厂
private static final CacheFactory<String , Object> factory = new DefaultCacheFactory<String , Object>();
//Jboss cache
private static Cache<String , Object> cache = null;
//默认JNDI工厂名
private String factoryClassName = "org.apache.naming.factory.BeanFactory";
public String getFactoryClassName() {
return factoryClassName;
}
public void setFactoryClassName(String factoryClassName) {
this.factoryClassName = factoryClassName;
}
public JNDIClusterCache(){
if(cache == null){
synchronized(JNDIClusterCache.class){
if(cache == null){
cache = factory.createCache(CONFIG_FILE_LOCATION);
}
}
}
}
/**
* 获取JNDI引用对象
*/
public Reference getReference() throws NamingException {
Reference ref=new Reference(getClass().getName(),getFactoryClassName(),null);
//添加Reference属性
//ref.add(new StringRefAddr("location",location));
//ref.add(new StringRefAddr("state",state));
return ref;
}
/**
* 打开cache的批处理事务
*/
public void beginBatchTx(){
cache.startBatch();
}
/**
* 提交cache的批处理事务
*/
public void commitBatchTx(){
cache.endBatch(true);
}
/**
* 回滚cache的批处理事务
*/
public void rollbackBatchTx(){
cache.endBatch(false);
}
/**
* 在指定的结点上缓存一个key-value型数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param key 缓存对象的键
* @param value 缓存对象
*/
public void cacheData(String nodePath , String key , Object value){
Fqn<String> fqn = Fqn.fromString(nodePath);
cache.put(fqn, key, value);
}
/**
* 在指定的结点上缓存整个Map数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param dataMap 缓存对象的Map
*/
public void cacheDataMap(String nodePath , Map<String , Object> dataMap){
Fqn<String> fqn = Fqn.fromString(nodePath);
cache.put(fqn, dataMap);
}
/**
* 获取指定结点上缓存的数据对象
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param key 缓存对象的键
* @return Object 返回缓存对象
*/
public Object getCacheData(String nodePath , String key){
Fqn<String> fqn = Fqn.fromString(nodePath);
return cache.get(fqn, key);
}
/**
* 获取指定结点上缓存的数据集
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @return Map 返回缓存的Map数据集
*/
public Map<String , Object> getCacheDataMap(String nodePath){
Fqn<String> fqn = Fqn.fromString(nodePath);
return cache.getData(fqn);
}
/**
* 清除指定结点上的缓存数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
*/
public void clearData(String nodePath){
Fqn<String> fqn = Fqn.fromString(nodePath);
Node<String,Object> node = cache.getNode(fqn);
node.clearData();
}
/**
* 取得指定结点上的缓存数据的记录数
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
*/
public int getDataSize(String nodePath){
Fqn<String> fqn = Fqn.fromString(nodePath);
Node<String,Object> node = cache.getNode(fqn);
return node.dataSize();
}
/**
* 获取指定结点上的子结点集合
* @param nodePath
* @return Set<Node<String,Object>> , 返回指定结点上的子结点集合
*/
public Set<Node<String,Object>> getChildren(String nodePath){
Fqn<String> fqn = Fqn.fromString(nodePath);
Node<String,Object> node = cache.getNode(fqn);
return node.getChildren();
}
/**
* 移出指定结点上的单个数据
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @param key 缓存对象的键
* @return Object 返回缓存对象
*/
public Object removeData(String nodePath , String key){
Fqn<String> fqn = Fqn.fromString(nodePath);
return cache.remove(fqn, key);
}
/**
* 移出整个的指定结点
* @param nodePath 缓存结点的路径 如:/aaa/bbb/ccc ,
* @return boolean true:成功移除结点 ; false:没有找到指定结点
*/
public boolean removeNode(String nodePath){
Fqn<String> fqn = Fqn.fromString(nodePath);
return cache.removeNode(fqn);
}
}
要说明的是,ClusterCache的实现中,对Jbosscache采用了单一实例。根据Jbosscache的API文档说明,其Cache的实现是ThreadSafe的。
2.在Tomcat的服务器全局域发布JbossCache的JNDI服务类
发布Tomcat的全局JNDI相信大家都做过,这里为了读者的思路连续,我再罗嗦一次吧。
发布jar包
进行全局JNDI的发布,需要将jar包拷贝到Tomcat的\lib目录下,因此我们要把上述的两个类打包成一个jar,比如:ClusterCache.jar,同时带上JbossCache的JAR包拷贝到lib目录下,JbossCache相关包清单如下:
- jbosscache-core-3.2.1
- commons-logging-1.1.1.jar
- jboss-common-core.jar
- jboss-logging-spi.jar
- jboss-transaction-api.jar ( 即jta.jar )
- jcip-annotations.jar
- jgroups.jar
此外,在JNDIClusterCache中,我们声明了jbosscache的配置文件为jbosscache.cfg.xml,因此我们也要把该配置文件拷到lib目录中。
配置Tomcat全局的资源
在Tomcat的\conf目录下,找到context.xml文件,在其中加上以下配置
<Resource name="ik/clustercache"
auth="Container"
type="com.wltea.cache.JNDIClusterCache"
factory="org.apache.naming.factory.BeanFactory"/>
配置Web App应用的客户端JNDI引用
在要使用JNDI Cache应用的web.xml中加上对cache资源的引用配置
<resource-ref>
<description>JNDI Cluster Cache</description>
<res-ref-name>ik/clustercache</res-ref-name>
<res-type>com.wltea.cache.ClusterCache</res-type>
<res-auth>Container</res-auth>
</resource-ref>
在应用取得JNDI上的JbossCache
// Put your code here
Context initContext;
try {
initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
ClusterCache cache = (ClusterCache)envContext.lookup("ik/clustercache");
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
到此为止,我们已经实现了将JbossCache发布到Tomcat的JNDI上。这样做的好处是,一个应用服务器只有一个JbossCache服务实例,群集服务器Cache间的同步将更有效率,内存及CPU资源消耗更少。
但这样做也有个问题,就是JNDI上的Jbosscache将无法使用分布在各个应用中CacheListener监听器。原因是Tomcat的全局lib的classLoader比web应用的classloader在类加载层次上更底层一些,简单的说,就是web-inf\lib中的类可以引用全局lib的类,但全局lib的类没法反向应用web-inf\lib中的jar类,除非你把CacheListener一起发布到全局的lib下。
【全文完】