J2Cache二级缓存框架源码阅读
因为最近在看Redis的设计与实现,想写一个用java实现的缓存框架,于是就找了一个开源的J2Cache框架源码来学习,这里是记录的第一篇文章。
文章索引
一.J2Cache缓存结构
因为其实我也没怎么看文档就开始看源码了,都是从源码中理解的,首先看一下J2Cache的目录结构,如图所示。
所有的缓存都是通过CacheProviderHolder来管理的,它是一个缓存管理器,我们知道,J2Cache是一个二级缓存框架,所以holder下会通过l1_provider和l2_provider来管理Level1Cache一级缓存和Level2Cache缓存,在一级缓存和二级缓存之下会通过区分不用的region区域来划分,不同的region下保存不用的key。
用图来说明就这么理解。
不同层级的缓存region和key名肯定是一样的。
二.J2Cache缓存对象继承关系
首先定义了一个Cache缓存接口,来看一下继承图一目了然。
底下实现了一级缓存和二级缓存。
- 一级缓存实现了如ehcache的缓存封装,实现了缓存操作以及对缓存数据失效的侦听,实现类为EhCache等等。
- 二级缓存实现了如Redis的缓存封装,redis是基于Jedis接口封装的,实现类为RedisHashCache等等。
不过在作为返回值是使用了CacheObject类,后面再说。
三.J2Cache初始化
1. J2Cache
J2Cache,它是缓存的入口,在测试用例中,也是使用它来调用getChannel来获取管道,调用缓存相关的方法,测试用例如下所示。
public static void main(String[] args) {
CacheChannel cache = J2Cache.getChannel();
//缓存操作
cache.set("default", "1", "Hello J2Cache");
System.out.println(cache.get("default", "1"));
cache.evict("default", "1");
System.out.println(cache.get("default", "1"));
cache.close();
}
所以我们先来看J2Cache的代码,在我的理解里,J2Cache它是一个面向使用者的类。
1.首先介绍它的成员变量。
- CONFIG_FILE : 不用多说,它只不过指定了配置文件的名称,来读取指定的配置文件来使用。
- builder : 这个类才是真正的大哥,所有的J2Cache的方法都是由它来实现的,所以J2CacheBuilder是一个面向开发者的一个类。
2.然后再来看它的静态代码块。
2.1 第一步来读取配置文件并实例化config对象,里面就是一顿IO操作,不解释。
2.2 第二步使用config对象来实例化builder,这里要注意一下,后面会详细说这个类。
这里调用了J2CacheBuilder的init静态方法,其中的操作就是赋值config对象,并返回builder实例化对象,还没有进行初始化builder,所以在它的init方法注释上说,这个操作很重,请勿重复执行是不是很正确的,只有在调用了它的getChannel方法,才会初始化builder。
介绍完成员变量和静态代码块,其他方法都是通过调用builder来实现操作的,所以我们接下来就来看一下J2CacheBuilder这个类吧。
public class J2Cache {
private final static String CONFIG_FILE = "/j2cache.properties";
private final static J2CacheBuilder builder;
static {
try {
J2CacheConfig config = J2CacheConfig.initFromConfig(CONFIG_FILE);
builder = J2CacheBuilder.init(config);
} catch (IOException e) {
throw new CacheException("Failed to load j2cache configuration " + CONFIG_FILE, e);
}
}
/**
* 返回缓存操作接口
* @return CacheChannel
*/
public static CacheChannel getChannel(){
return builder.getChannel();
}
/**
* 关闭 J2Cache
*/
public static void close() {
builder.close();
}
}
1. J2CacheBuilder
J2CacheBuilder,它才是真正进行初始化操作,获取管道的工厂类,注解上说是使用自定义配置构建 J2Cache,那我们来看一下他是如何进行初始化的吧。
介绍一下它的成员变量,解释都写在注解上了,后面会一步步初始化赋值的。
private final static Logger log = LoggerFactory.getLogger(J2CacheBuilder.class); // 日志对象
private CacheChannel channel; // 缓存操作的管道
private CacheProviderHolder holder; // 缓存管理
private ClusterPolicy policy; //不同的广播策略
private AtomicBoolean opened = new AtomicBoolean(false); // 管道是否打开
private J2CacheConfig config; // 传入的配置文件对象
首先从上文说道,在静态代码块中,调用了J2CacheBuilder的init静态方法,这个方法是这个样子的,它返回了一个builder实例化对象。
public final static J2CacheBuilder init(J2CacheConfig config) {
return new J2CacheBuilder(config);
}
构造方法也只是进行了赋值,并没有进行初始化。
private J2CacheBuilder(J2CacheConfig config) {
this.config = config;
}
所以,我们再来看getChannel方法,这个是用来获取管道的方法,以后都是通过管道来进行缓存操作的。
我们可以看到,在双重校验之后(保证线程安全),会调用initFromConfig方法进行初始化,该私有方法主要是对holder缓存管理对象、policy不同的广播策略(用于集群)进行赋值。
- 初始化holder对象时,会传入config配置文件,和CacheExpiredListener监听接口, 在holder的init方法中,listener进行直接赋值,config用于读取一级缓存的名称,比如配置文件中默认为“caffeine”,二级缓存的名称,比如配置文件中默认为“redis”,以此来通过不同的策略来实例化不同的CacheProvider,即l1_provider和l2_provider。
- 初始化policy对象同理,通过工厂模式匹配不同的配置参数来实例化对应的ClusterPolicy。【这块我没有细看】
这样我们的initFromConfig方法就执行完毕,初始化好了这两个对象,然后会实例化单例的CacheChannel对象,实现三个未实现的抽象方法,然后将opened设为true,即为管道打开。
/**
* 返回缓存操作接口
*
* @return CacheChannel
*/
public CacheChannel getChannel() {
/**
* 做了双层校验,为了单例生成CacheChannel缓存操作接口
* tip : CacheChannel 虽然是一个管道,但是包括了region等方法,总体来说就是一个缓存操作的接口
*/
if (this.channel == null || !this.opened.get()) {
synchronized (J2CacheBuilder.class) {
if (this.channel == null || !this.opened.get()) {
this.initFromConfig(config); // 这里进行加载配置初始化
/* 初始化缓存接口 */
this.channel = new CacheChannel(config, holder) {
@Override
public void sendClearCmd(String region) {
policy.sendClearCmd(region);
}
@Override
public void sendEvictCmd(String region, String... keys) {
policy.sendEvictCmd(region, keys);
}
@Override
public void close() {
super.close();
policy.disconnect();
holder.shutdown();
opened.set(false);
}
};
this.opened.set(true);
}
}
}
return this.channel;
}
/**
* 加载配置
*
* @return
* @throws IOException
*/
private void initFromConfig(J2CacheConfig config) {
// 配置中默认为“json”
SerializationUtils.init(config.getSerialization(), config.getSubProperties(config.getSerialization()));
//初始化两级的缓存管理 (配置, 监听)
this.holder = CacheProviderHolder.init(config, (region, key) -> {
//当一级缓存中的对象失效时,自动清除二级缓存中的数据
Level2Cache level2 = this.holder.getLevel2Cache(region);
level2.evict(key);
if (!level2.supportTTL()) {
//再一次清除一级缓存是为了避免缓存失效时再次从 L2 获取到值
this.holder.getLevel1Cache(region).evict(key);
}
log.debug("Level 1 cache object expired, evict level 2 cache object [{},{}]", region, key);
if (policy != null)
policy.sendEvictCmd(region, key);
});
policy = ClusterPolicyFactory.init(holder, config.getBroadcast(), config.getBroadcastProperties());
log.info("Using cluster policy : {}", policy.getClass().getName());
}
四.总结
通过传入的config对象实例化了序列化器、holder缓存管理器、policy广播策略、channel管道,之后就是通过调用channel实现具体的操作缓存方法。