装饰器模式
装饰器模式(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 也没问题。
源码中,哪里体现了 SynchronizedCache 必备?
类 CacheBuilder
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
cache = new SerializedCache(cache);
}
cache = new LoggingCache(cache);
//这里
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}