MyBatis缓存设计使用了装饰模式和责任链模式。本文将分析MyBatis缓存对象的结构和创建过程。
使用MyBatis缓存,首先要在mapper.xml文件中配置<cache>节点,当前命名空间下就开启了缓存。开启缓存的第一步就是创建缓存的容器,可以使用自定义的缓存容器,MyBatis默认提供PerpetualCache为缓存容器。创建缓存容器的过程首先是解析XML中的<cache>节点。
/**
* 解析<cache>节点
* @param context 节点对象
* @throws Exception
*/
private void cacheElement(XNode context) throws Exception {
// 配置了<cache>节点
if (context != null) {
// 基础缓存的类型,可以自定义,默认为PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
// 加载类,如果配置错误,这里将抛找不到类异常
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 缓存回收算法,默认为最少使用回收LRU
String eviction = context.getStringAttribute("eviction", "LRU");
// 同样去加载算法类
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 缓存自动刷新时间
Long flushInterval = context.getLongAttribute("flushInterval");
// 缓存大小
Integer size = context.getIntAttribute("size");
// 是否只读
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 其他配置属性
Properties props = context.getChildrenAsProperties();
// 创建缓存
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
}
}
创建的过程使用建造者模式,通过CacheBuilder来创建Cache对象。创建结束后,将Cache添加都configuration对象,这是全局配置对象,存在整个MyBatis的生命周期中。
/**
* 创建新缓存容器
* @param typeClass 基础缓存容器
* @param evictionClass 缓存回收算法
* @param flushInterval 有效时间
* @param size 容器大小
* @param readWrite 是否只读
* @param props 其他属性
* @return 缓存容器
*/
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
Properties props) {
// 如果为空设置默认值
typeClass = valueOrDefault(typeClass, PerpetualCache.class);
evictionClass = valueOrDefault(evictionClass, LruCache.class);
// 各个属性传入容器建造者,调用build创建缓存容器
Cache cache = new CacheBuilder(currentNamespace)
.implementation(typeClass)
.addDecorator(evictionClass)
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.properties(props)
.build();
// 将缓存容器添加到全局配置对象的范围
configuration.addCache(cache);
// 将当前缓存容器到Mapper的创建助手
currentCache = cache;
return cache;
}
<pre name="code" class="java"> /**
* 创建缓存容器
* @return 缓存容器
*/
public Cache build() {
// 设置基础缓存容器,回收算法的默认实现
setDefaultImplementations();
// implementation是一个Class对象,通过反射创建缓存容器对象,并设置id
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
if (PerpetualCache.class.equals(cache.getClass())) { // issue #352, do not apply decorators to custom caches
for (Class<? extends Cache> decorator : decorators) {
// 回收算法容器
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
// 构造标准缓存链
<span> </span>cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
// 日志容器
cache = new LoggingCache(cache);
}
// 返回一个缓存容器。
return cache;
}
首先是通过反射创建基础的容器,有了基础容器的Class类型,通过反射取得构造参数为String的构造方法,然后将id传入,完成基础容器的构建。
/**
* 通过反射获得基础缓存容器对象
* @param cacheClass 缓存类型
* @param id 标识符
* @return 基础缓存容器对象
*/
private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
// 获取该类的构造方法
Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
try {
// 创建对象
return cacheConstructor.newInstance(id);
} catch (Exception e) {
throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
}
}
/**
* 获取构造函数
* @param cacheClass 缓存类型
* @return 缓存类型的构造函数
*/
private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {
try {
return cacheClass.getConstructor(String.class);
} catch (Exception e) {
throw new CacheException("Invalid base cache implementation (" + cacheClass + "). " +
"Base cache implementations must have a constructor that takes a String id as a parameter. Cause: " + e, e);
}
}
这个基础的容器该容器使用Map<Object,Object>来放缓存,3.2.6版本之前有一把读写锁,客户端可以通过get方法获取该锁,然后对数据进行操作,之后的版本取消了读写锁。
回到builder方法,创建基础容器结束后,下一步要设置缓存回收算法cache = newCacheDecoratorInstance(decorator, cache),还是使用反射,获得构造方法参数为Class,Cache的构造方法,不仅需要算法的类型,而且构造函数里面还需要一个缓存容器对象Cache。看一下LruCache类的成员。
// 代理对象
private final Cache delegate;
// 用于实现lru算法的map
private Map<Object, Object> keyMap;
// 最近最少使用的键
private Object eldestKey;
外部传进来的基础容器对象,在这里面成了被代理的对象,而且维护一个keyMap来实现lru算法。因为LruCache和PerpetualCache都实现了Cache接口,所以在使用LruCache的时候,调用的是被代理PerpetualCache的方法。
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
特殊的地方在于创建时要维护的keyMap,已经取缓存操作,放缓存操作时,要计算缓存使用的频率,然后清空较少使用的缓存。
/**
* 初始化时设置长度
* @param size 默认1024
*/
public void setSize(final int size) {
// 使用LinkedHashMap来实现LRU算法
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
// 重写removeEldestEntry方法
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
// 缓存多于预定的数量
boolean tooBig = size() > size;
if (tooBig) {
// 缓存中最少使用的键
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
通过get、set取缓存,并且维护缓存容器。
@Override
public Object getObject(Object key) {
// 执行一个get操作,记录key的使用
keyMap.get(key);
// 通过代理从基础缓存容器中取出真正的缓存对象
return delegate.getObject(key);
}
@Override
public void putObject(Object key, Object value) {
// 将对象放入基础缓存容器
delegate.putObject(key, value);
// 维护lru算法map
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
// 放入key
keyMap.put(key, key);
// 如果存在要异常的对象,调用代理容器移除缓存
if (eldestKey != null) {
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
回到之前的cache = newCacheDecoratorInstance(decorator, cache);返回的Cache对象将基础缓存容器包装了一层,对lruCache操作时,维护lru算法,然后通过代理对象执行真正的取缓存、放缓存操作。
接下来build()方法中还执行了cache = setStandardDecorators(cache);接着对cache执行包装。
/**
* 设置标准装饰器
* @param cache 缓存容器
* @return 缓存容器
*/
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
// 装饰定时功能
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
// 装饰序列化功能
cache = new SerializedCache(cache);
}
// 装饰日志功能
cache = new LoggingCache(cache);
// 装饰同步功能
cache = new SynchronizedCache(cache);
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
通过一层层的装饰,形成了一条责任链,链上每个cache完成自己的功能,然后委托下一节点进行操作。