Question:
. Cache 接口的定义和实现
. 含有FIFO, Logging, LRU 等特性的Cache如何实现
. 如何利用装饰模式保证 某一个Cache 含有 某几个 特性
. CacheKey的设计与实现
Cache接口定义:
/**
* SPI for cache providers.
*
* One instance of cache will be created for each namespace.
*
* The cache implementation must have a constructor that receives the cache id as an String parameter.
*
* MyBatis will pass the namespace as id to the constructor.
*
* <pre>
* public MyCache(final String id) {
* if (id == null) {
* throw new IllegalArgumentException("Cache instances require an ID");
* }
* this.id = id;
* initialize();
* }
* </pre>
*
* @author Clinton Begin
*/
public interface Cache {
/**
* @return The identifier of this cache
*/
String getId();
/**
* @param key Can be any object but usually it is a{@link CacheKey}
* @param value The result of a select.
*/
void putObject(Object key, Objectvalue);
/**
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
/**
* Optional. It is not called by the core.
*
* @param key The key
* @return The object that was removed
*/
Object removeObject(Object key);
/**
* Clears this cache instance
*/
void clear();
/**
* Optional. This method is not called by the core.
*
* @return The number of elements stored in the cache (not its capacity).
*/
int getSize();
/**
* Optional. As of 3.2.6 this method is no longer called by the core.
*
* Any locking needed by the cache must be provided internally by the cache provider.
*
* @return A ReadWriteLock
*/
ReadWriteLock getReadWriteLock();
}
从类注解上可以 看出, mybaits 二级缓存的实现 是针对 每一个 mapper的 namespace id 定义了一个全局的cache, 也就是说 mybatis 是根据 namespace id 来 区分不同的 缓存的,同时,在 putObject 注解上 也说了, 可以 用任意对象作为 key, 但 在mybatis 基本上是用 CacheKey 实例作为key,
Cache接口实现类:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
/**
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>(); // Cache一切的实现都是由 这个HashMap 来代理实现
public PerpetualCache(String id) {
this.id = id;
}
public String getId() {
return id;
}
public int getSize() {
return cache.size();// cache的 size方法代理
}
public void putObject(Object key, Object value) {
cache.put(key, value);// cache的 put方法代理
}
public Object getObject(Object key) {
return cache.get(key);// cache的 get方法代理
}
public Object removeObject(Object key) {
return cache.remove(key);// cache的 remove方法代理
}
public void clear() {
cache.clear();// cache的 clear方法代理
}
public ReadWriteLock getReadWriteLock() {
return null;// 返回空, Cache接口上getReadWriteLock说的很清楚, 3.2.6版本后即不再调用这个方法,由外部来保证同步
}
public boolean equals(Object o) {
if (getId() == null) throw new CacheException("Cache instances require an ID.");
if (this == o) return true;
if (!(o instanceof Cache)) return false;
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId()); // 只根据 id判断 两个 cache是否相同
}
public int hashCode() {
if (getId() == null) throw new CacheException("Cache instances require an ID.");
return getId().hashCode();// 只根据 id 获取 hashcode
}
}
从中可以看出 PerpetualCache 是 组合了 HashMap 来实现了 Cache接口,
对于 FIFO,Logging, LRU 等功能,下面一一道来:
FIFO:
定义 keyList
private LinkedList<Object> keyList; // 利用 LinkedList 来 记录 key的先后顺序
同时设置 FifoCache的大小:
public void setSize(intsize) {
this.size = size;
}
接着在 putObject方法中 先将key加入到尾部,同时 检查key的大小 是否 超过设置的大小,如果超过了大小,则将keyList头部删除,同时 根据key删除缓存,最后 将新增对象加入缓存
@Override
public void putObject(Objectkey, Object value) {
cycleKeyList(key);// 现将新增key加入 keylist 尾部,检查 是否 超过了大小,如果超过了大小,则将头部删除,同时 根据key删除缓存,最后 将新增对象加入缓存
delegate.putObject(key,value);//最后 将新增对象加入缓存
}
private void cycleKeyList(Objectkey) {
keyList.addLast(key);// 现将新增key加入 keylist 尾部,
if (keyList.size() >size) {//检查 是否 超过了大小,
Object oldestKey = keyList.removeFirst();//将头部删除,同时 根据key删除缓存
delegate.removeObject(oldestKey);
}
}
问题:
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
看到了吗? FifoCache 在 删除某一个 key的时候 并没有 删除 keyList上的键, 虽然 有size 可以保证 keyList 不会超过 size, 但 已经不能保证 keyList 和 Cache下的 hashMap的 keySet 同步了, 当然,在 其clear 方法,会将 keyList 和 cache 同时删除, 这样设计 可能是 为了 简化 removeObject 的缘故吧
@Override
public void clear() {
delegate.clear();
keyList.clear();
}
LRU:
利用LinkedHashMap 的 accessOrder属性来实现,
首先定义了 两个属性
private Map<Object, Object> keyMap; // linkedHashMap 实例
private Object eldestKey;// 在 超过 size后,被 keyMap淘汰出局的key
通是在 setSize 方法中 实例化了 keyMap对象:
public void setSize(finalint size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F,true) {
private staticfinal long serialVersionUID = 4267176411845948333L;
protected boolean removeEldestEntry(Map.Entry<Object, Object>eldest) {// 覆盖父类的removeEldestEntry 方法
boolean tooBig = size() >size; // 如果 keyMap的size 超过了 定义的 size大小
if (tooBig) {
eldestKey = eldest.getKey();// 则将 最少访问的key 赋值给 eldestKey
}
return tooBig;
}
};
}
同时在 putObject 方法中,加入 cycleKeyList 方法:
@Override
public void putObject(Objectkey, Object value) {
delegate.putObject(key,value);
cycleKeyList(key);
}
private void cycleKeyList(Objectkey) {
keyMap.put(key,key); // 直接将 键为key 值为key 插入到 keyMap中
if (eldestKey !=null) {// 如果 eldestKey 不为空,则说明 已经 超过了 设定的size
delegate.removeObject(eldestKey);// 将淘汰出局的key从 缓存中 删除
eldestKey = null;// 重置为 nulll
}
}
这些特性的实现是不是很容易? 至于其他的特性,包括 logging, schedualed等 花点时间是很容易看懂的
装饰模式 添加 特性:
mybatis 对cache的 特性实现 利用了 装饰模式, 每一个 特性的实现都是在原来的delegate 类基础上添加 功能
CacheKey的设计和实现:
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @author Clinton Begin
*/
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
private int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<Object>();
}
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
public int getUpdateCount() {
return updateList.size();
}
public void update(Object object) {
if (object != null && object.getClass().isArray()) {
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object element = Array.get(object, i);
doUpdate(element);
}
} else {
doUpdate(object);
}
}
private void doUpdate(Object object) {
int baseHashCode = object == null ? 1 : object.hashCode();
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
public boolean equals(Object object) {
if (this == object)
return true;
if (!(object instanceof CacheKey))
return false;
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode)// 首先 根据 hashcode 判断是否相等
return false;
if (checksum != cacheKey.checksum)// 接着 根据 checksum 判断是否相等
return false;
if (count != cacheKey.count)// 接着 根据 count 判断是否相等
return false;
for (int i = 0; i < updateList.size(); i++) {// 最后 根据 updateList 判断是否相等
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (thisObject == null) {
if (thatObject != null)
return false;
} else {
if (!thisObject.equals(thatObject))
return false;
}
}
return true;
}
public int hashCode() {
return hashcode;
}
public String toString() {
StringBuilder returnValue = new StringBuilder().append(hashcode).append(':').append(checksum);
for (int i = 0; i < updateList.size(); i++) {
returnValue.append(':').append(updateList.get(i));
}
return returnValue.toString();
}
@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<Object>(updateList);
return clonedCacheKey;
}
}
计算 equals 方法是否相等, 是不是 很 详细? 层层递进!