結合 Mybatis 的 cache 包源碼理解裝飾器模式

裝飾器模式

裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,是一種用於代替繼承的技術,無需通過繼承增加子類就能擴展對象的新功能。使用對象的關聯關係代替繼承關係,更加靈活,同時避免類型體系的快速膨脹。

結構

在這裏插入圖片描述

角色

  • 抽象構件角色(Component):給出一個抽象接口,以規範準備接收附加責任的對象。

  • 具體構件角色(ConcreteComponent):定義一個將要接受附加責任的對象。

  • 裝飾角色(Decorator):持有一個構件(Component)對象的引用,並定義一個與抽象構件接口一致的接口。

  • 具體裝飾角色(ConcreteDecorator):負責給構件對象貼上附加的責任。

優勢

  • 靈活性:裝飾器模式將功能切分成一個個獨立的裝飾器,在運行期可以根據需要動態的添加功能,甚至對添加的新功能進行自由的組合
  • 擴展性:當有新功能要添加的時候,只需要添加新的裝飾器實現類,然後通過組合方式添加這個新裝飾器,無需修改已有代碼,符合開閉原則

cache 包裏的裝飾器模式

在這裏插入圖片描述

在這裏插入圖片描述

抽象構件 Cache

public interface Cache {

  String getId();//緩存實現類的id

  void putObject(Object key, Object value);//往緩存中添加數據,key一般是CacheKey對象

  Object getObject(Object key);//根據指定的key從緩存獲取數據

  Object removeObject(Object key);//根據指定的key從緩存刪除數據

  void clear();//清空緩存

  int getSize();//獲取緩存的個數

  ReadWriteLock getReadWriteLock();//獲取讀寫鎖

}

具體構件 PerpetualCache

public class PerpetualCache implements Cache {

  private final String id;
  //緩存就是一個HashMap
  private Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  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());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }
}

具體裝飾 BlockingCache

選了 BlockingCache 這個具體裝飾作爲代表
這個裝飾器保證只有一個線程到數據庫去查找指定的key對應的數據,解決了緩存擊穿問題。

public class BlockingCache implements Cache {

  //阻塞的超時時長
  private long timeout;
  //被裝飾的底層對象,一般是PerpetualCache
  private final Cache delegate;
  //鎖對象集,粒度到key值
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      releaseLock(key);
    }
  }

  @Override
  public Object getObject(Object key) {
    acquireLock(key);//根據key獲得鎖對象,獲取鎖成功加鎖,獲取鎖失敗阻塞一段時間重試
    Object value = delegate.getObject(key);
    if (value != null) {//獲取數據成功的,要釋放鎖
      releaseLock(key);
    }
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    releaseLock(key);
    return null;
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  private ReentrantLock getLockForKey(Object key) {
    ReentrantLock lock = new ReentrantLock();//創建鎖
    ReentrantLock previous = locks.putIfAbsent(key, lock);//把新鎖添加到locks集合中,如果添加成功使用新鎖,如果添加失敗則使用locks集合中的鎖
    return previous == null ? lock : previous;
  }

//根據key獲得鎖對象,獲取鎖成功加鎖,獲取鎖失敗阻塞一段時間重試
  private void acquireLock(Object key) {
	//獲得鎖對象
    Lock lock = getLockForKey(key);
    if (timeout > 0) {//使用帶超時時間的鎖
      try {
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {//如果超時拋出異常
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    } else {//使用不帶超時時間的鎖
      lock.lock();
    }
  }

  private void releaseLock(Object key) {
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }
}

具體裝飾 SynchronizedCache

另外,SynchronizedCache 也非常重要,看了 Mybatis 的一級緩存與二級緩存 這篇文章都知道,二級緩存是跨 SqlSession 的,而一個 SqlSession 對應着一個連接,所以要給 Cache 加上併發安全控制。

具體構件 PerpetualCache 裏 cache 是一個 HashMap,並沒有併發安全控制,那麼 Mybatis 把併發安全控制做到哪裏了呢?

通過 debug 代碼可以知道,每當拿到一個二級緩存 Cache,都至少被 SynchronizedCache 裝飾過了。

public class SynchronizedCache implements Cache {

  private final Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public synchronized int getSize() {
    return delegate.getSize();
  }

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public synchronized void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
}

這個具體裝飾也很簡單,就是在可能有併發安全問題的方法上加上 synchronized 進行同步。

至於一級緩存,並沒有被這個裝飾器裝飾,因爲一級緩存存在於 SqlSession 的生命週期中。也就是,一個線程,一個連接,一個一級緩存,不存在併發安全問題,用 HashMap 也沒問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章