MyBatis源碼分析(3)—— Cache接口以及實現


@(MyBatis)[Cache]

MyBatis源碼分析——Cache接口以及實現

Cache接口

MyBatis中的Cache以SPI實現,給需要集成其它Cache或者自定義Cache提供了接口。

public interface Cache {	
  String getId();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  int getSize();
  ReadWriteLock getReadWriteLock();
}

Cache實現

Cache的實現類中,Cache有不同的功能,每個功能獨立,互不影響,則對於不同的Cache功能,這里使用了裝飾者模式實現。

PerpetualCache

作為為最基礎的緩存類,底層實現比較簡單,直接使用了HashMap。

FifoCache

FIFO回收策略,裝飾類,內部維護了一個隊列,來保證FIFO,一旦超出指定的大小,則從隊列中獲取Key並從被包裝的Cache中移除該鍵值對。

public class FifoCache implements Cache {

  // 被包裝的類
  private final Cache delegate;
  // 隊列,用來維持FIFO
  private LinkedList<Object> keyList;
  // 最大可容納的大小
  private int size;

  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList<Object>();
    this.size = 1024;
  }

  @Override
  public void putObject(Object key, Object value) {
    // 將Key放入隊列中,並且檢查一遍,如果滿了則移除隊列頭部的元素
    cycleKeyList(key);
    // 執行真正的操作
    delegate.putObject(key, value);
  }

  private void cycleKeyList(Object key) {
    // 將Key放入隊列
    keyList.addLast(key);
    if (keyList.size() > size) {
      // 超出指定容量,移除隊列頭部Key
      Object oldestKey = keyList.removeFirst();
      // 從緩存中移除Key對應的值
      delegate.removeObject(oldestKey);
    }
  }
  
  // 省略部分代碼
  ...
}

LoggingCache

日志功能,裝飾類,用於記錄緩存的命中率,如果開啟了DEBUG模式,則會輸出命中率日志。

public class LoggingCache implements Cache {

  private Log log;  
  private Cache delegate;
  // 請求次數
  protected int requests = 0;
  // 命中次數
  protected int hits = 0;

  public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(getId());
  }

  @Override
  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      // 命中
      hits++;
    }
    if (log.isDebugEnabled()) {
      // 如果開啟了DEBUG模式,則輸出命中率
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

  // 獲取命中率
  private double getHitRatio() {
    return (double) hits / (double) requests;
  }

  // 省略部分代碼
  ...
}

LruCache

LRU回收策略,裝飾類,在內部保存一個LinkedHashMap,用以實現LRU。

public class LruCache implements Cache {

  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    // 初始化設置LRU回收的邊界容量
    setSize(1024);
  }

  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      // 鍵值移除策略,當大於指定容量時則移除最近最少使用的key/value
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          // 保存需要移除的鍵,因為在被包裝的類中並不知道什么鍵需要移除
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    // 將當前Key放到LRU的Map中,如果大於指定容量,則移除篩選的鍵值對
    cycleKeyList(key);
  }

  @Override
  public Object getObject(Object key) {
    // 讓當前LRU的Map知道使用過
    keyMap.get(key); //touch
    return delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    // 這里沒有移除當前維護的key,不過在后續也會被回收,可以忽略
    return delegate.removeObject(key);
  }

  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      // 從Cache中移除掉LRU篩選出的鍵值對
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }
  // 省略部分代碼...
}

ScheduledCache

定時清空Cache,但是並沒有開始一個定時任務,而是在使用Cache的時候,才去檢查時間是否到了。

public class ScheduledCache implements Cache {

  private Cache delegate;
  // 清除的時間間隔
  protected long clearInterval;
  // 上一次清除的時間
  protected long lastClear;

  public ScheduledCache(Cache delegate) {
    this.delegate = delegate;
    this.clearInterval = 60 * 60 * 1000; // 1 hour
    this.lastClear = System.currentTimeMillis();
  }

  public void setClearInterval(long clearInterval) {
    this.clearInterval = clearInterval;
  }

  @Override
  public Object getObject(Object key) {
    if (clearWhenStale()) {
      return null;
    } else {
      return delegate.getObject(key);
    }
  }

  private boolean clearWhenStale() {
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      // 時間到了,清空
      clear();
      return true;
    }
    return false;
  }
  
  @Override
  public void clear() {
    // 更新清空時間
    lastClear = System.currentTimeMillis();
    delegate.clear();
  }
  // 省略部分代碼
}

SynchronizedCache

同步Cache,實現比較簡單,直接使用synchronized修飾方法。

public class SynchronizedCache implements Cache {

  private Cache delegate;
  
  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }
  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }
  // 省略部分代碼
}

SoftCache

軟引用回收策略,軟引用只有當內存不足時才會被垃圾收集器回收。這里的實現機制中,使用了一個鏈表來保證一定數量的值即使內存不足也不會被回收,但是沒有保存在該鏈表的值則有可能會被回收。
在WeakHashMap中,可以看到是將引用應用到Key的,當Key被回收后,則移除相關的Value。但是這里是將其應用到Value中,因為Key不能被回收,如果被移除的話,就會影響到整個體系,最底層的實現使用HashMap實現的,沒有Key,就沒有辦法移除相關的值。反過來,值被回收了,將軟引用對象放到隊列中,可以根據Key調用removeObject移除該關聯的鍵和軟引用對象。

public class SoftCache implements Cache {
  // 用於保存一定數量強引用的值
  private final LinkedList<Object> hardLinksToAvoidGarbageCollection;
  // 引用隊列,當被垃圾收集器回收時,會將軟引用對象放入此隊列
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  private final Cache delegate;
  // 保存強引用值的數量
  private int numberOfHardLinks;

  public SoftCache(Cache delegate) {
    this.delegate = delegate;
    this.numberOfHardLinks = 256;
    this.hardLinksToAvoidGarbageCollection = new LinkedList<Object>();
    this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
  }

  @Override
  public void putObject(Object key, Object value) {
    // 移除被垃圾收集器回收的鍵值
    removeGarbageCollectedItems();
    // 將軟件用作用到Value中
    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
  }

  @Override
  public Object getObject(Object key) {
    Object result = null;
    @SuppressWarnings("unchecked")
    SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
    if (softReference != null) {
      result = softReference.get();
      if (result == null) {
        // 該值被垃圾收集器回收,移除掉該項
        delegate.removeObject(key);
      } else {
        // 這里以及下面的clear,想不通為什么要加hardLinksToAvoidGarbageCollection的同步?(在WeakCache中卻沒有加同步)
        synchronized (hardLinksToAvoidGarbageCollection) {
          hardLinksToAvoidGarbageCollection.addFirst(result);
          if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
            // 超出容量,則移除最先保存的引用
            hardLinksToAvoidGarbageCollection.removeLast();
          }
        }
      }
    }
    return result;
  }

  @Override
  public Object removeObject(Object key) {
    // 移除被垃圾收集器回收的鍵值
    removeGarbageCollectedItems();
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    synchronized (hardLinksToAvoidGarbageCollection) {
      // 這里需要清空該隊列,否則即使下面調用clear,其Map清空了,但是部分值保留有引用,垃圾收集器也不會回收,會造成短暫的內存泄漏。
      hardLinksToAvoidGarbageCollection.clear();
    }
    removeGarbageCollectedItems();
    delegate.clear();
  }

  private void removeGarbageCollectedItems() {
    SoftEntry sv;
    // 清空被垃圾收集器回收的value其相關聯的鍵以及軟引用
    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
      delegate.removeObject(sv.key);
    }
  }

  // 這里軟引用對象是用在value中的
  private static class SoftEntry extends SoftReference<Object> {
    // 保存與value相關聯的Key,因為一旦被垃圾收集器回收,則此軟引用對象會被放到關聯的引用隊列中,這樣就可以根據Key,移除該鍵值對。
    private final Object key;

    private SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
      super(value, garbageCollectionQueue);
      this.key = key;
    }
  }
  // 省略部分代碼
}

WeakCache

弱引用回收策略,弱引用的對象一旦被垃圾收集器發現,則會被回收,無論內存是否足夠。這里的實現和上面的軟引用類似,除了使用WeakReference替換掉SoftReference,其它基本一樣。還有一點想不通的就是,為什么SoftCache加鎖了,而這里沒有加鎖。

  public Object getObject(Object key) {
    Object result = null;
    @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
    WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
    if (weakReference != null) {
      result = weakReference.get();
      if (result == null) {
        delegate.removeObject(key);
      } else {
        // 軟引用這里加鎖了
        hardLinksToAvoidGarbageCollection.addFirst(result);
        if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
          hardLinksToAvoidGarbageCollection.removeLast();
        }
      }
    }
    return result;
  }

TransactionalCache

事務緩存,在提交的時候,才真正的放到Cache中,或者回滾的時候清除掉,對Cache沒有影響。

public class TransactionalCache implements Cache {

  private Cache delegate;
  private boolean clearOnCommit;
  private Map<Object, AddEntry> entriesToAddOnCommit;
  private Map<Object, RemoveEntry> entriesToRemoveOnCommit;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<Object, AddEntry>();
    this.entriesToRemoveOnCommit = new HashMap<Object, RemoveEntry>();
  }

  @Override
  public Object getObject(Object key) {
    if (clearOnCommit) return null; // issue #146
    return delegate.getObject(key);
  }

  @Override
  public void putObject(Object key, Object object) {
    // 移除當前事務中 待移除鍵值對操作
    entriesToRemoveOnCommit.remove(key);
    // 添加當前事務中 待增加到緩存鍵值對操作
    entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object));
  }

  @Override
  public Object removeObject(Object key) {
    // 移除增加該鍵值對的操作
    entriesToAddOnCommit.remove(key);
    // 添加移除鍵值對操作
    entriesToRemoveOnCommit.put(key, new RemoveEntry(delegate, key));
    return delegate.getObject(key);
  }

  @Override
  public void clear() {
    reset();
    clearOnCommit = true;
  }

  public void commit() {
    if (clearOnCommit) {
      // 當提交事務時需要先清空,則清空緩存
      delegate.clear();
    } else {
      // 應用移除鍵值對操作
      for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
        entry.commit();
      }
    }
    // 應用添加鍵值對操作
    for (AddEntry entry : entriesToAddOnCommit.values()) {
      entry.commit();
    }
    reset();
  }

  public void rollback() {
    reset();
  }

  private void reset() {
    clearOnCommit = false;
    entriesToRemoveOnCommit.clear();
    entriesToAddOnCommit.clear();
  }

  private static class AddEntry {
    private Cache cache;
    private Object key;
    private Object value;

    public AddEntry(Cache cache, Object key, Object value) {
      this.cache = cache;
      this.key = key;
      this.value = value;
    }

    public void commit() {
      // 提交的時候,才真正放入緩存
      cache.putObject(key, value);
    }
  }

  private static class RemoveEntry {
    private Cache cache;
    private Object key;

    public RemoveEntry(Cache cache, Object key) {
      this.cache = cache;
      this.key = key;
    }

    public void commit() {
      // 提交的時候才真正從緩存中移除
      cache.removeObject(key);
    }
  }
}

SerializedCache

序列化功能,將值序列化后存到緩存中。該功能用於緩存返回一份實例的Copy,用於保存線程安全。

public class SerializedCache implements Cache {
  // 省略部分代碼	

  @Override
  public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      // 先序列化后再存放到緩存中
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
  }

  @Override
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    // 不為空,則反序列化,生成一份Copy
    return object == null ? null : deserialize((byte[]) object);
  }

  private byte[] serialize(Serializable value) {
    try {
      // 序列化
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(bos);
      oos.writeObject(value);
      oos.flush();
      oos.close();
      return bos.toByteArray();
    } catch (Exception e) {
      throw new CacheException("Error serializing object.  Cause: " + e, e);
    }
  }

  private Serializable deserialize(byte[] value) {
    Serializable result;
    try {
      // 反序列化
      ByteArrayInputStream bis = new ByteArrayInputStream(value);
      ObjectInputStream ois = new CustomObjectInputStream(bis);
      result = (Serializable) ois.readObject();
      ois.close();
    } catch (Exception e) {
      throw new CacheException("Error deserializing object.  Cause: " + e, e);
    }
    return result;
  }

  public static class CustomObjectInputStream extends ObjectInputStream {

    public CustomObjectInputStream(InputStream in) throws IOException {
      super(in);
    }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
      // 此方法只有在待序列化的類第一次序列化的時候才會被調用
      // 遍歷所支持的ClassLoader,加載對應的Class
      return Resources.classForName(desc.getName());
    }
  }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM