裝飾器模式
裝飾器模式(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 也沒問題。