mybatis 源码系列 组件之 cache

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 方法是否相等, 是不是 很 详细? 层层递进!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值