一、Mybatis缓存模块分析
===============
mybatis缓存模块具备以下特点:
-
MyBatis 缓存的实现是基于 Map 的,从缓存里面读写数据是缓存模块的核心基础功能;
-
除核心功能之外,有很多额外的附加功能,如:防止缓存击穿,添加缓存清空策略(fifo、 lru)、序列化功能、日志能力、定时清空能力等;
-
附加功能可以以任意的组合附加到核心基础功能之上;
那么我们应该如何优雅的为核心功能添加附加能力呢?
有些同学可能了解使用继承的办法扩展附加功能,继承的方式是静态的,用户不能控制增加行为的方式和时机。另外,新功能的存在多种组合,使用继承可能导致大量子类存在;
基于 Map 核心缓存能力,将阻塞、清空策略、序列化、日志等等能力以任意组合的方式优 雅的增强是 Mybatis 缓存模块实现最大的难题,用动态代理或者继承的方式扩展多种附加能力的传统方式存在以下问题:这些方式是静态的,用户不能控制增加行为的方式和时机;另 外,新功能的存在多种组合,使用继承可能导致大量子类存在。综上,MyBtis 缓存模块采用 了装饰器模式实现了缓存模块;
二、装饰器模式
========
1、装饰器模式介绍
装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使 用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。装饰器 UML 类图如下:
组件含义如下:
-
组件(Component):组件接口定义了全部组件类和装饰器实现的行为;
-
组件实现类(ConcreteComponent):实现 Component 接口,组件实现类就是被装饰器 装饰的原始对象,新功能或者附加功能都是通过装饰器添加到该类的对象上的
-
装饰器抽象类(Decorator):实现 Component 接口的抽象类,在其中封装了一个 Component 对象,也就是被装饰的对象;
-
具体装饰器类(ConcreteDecorator):该实现类要向被装饰的对象添加某些功能;
我们很多人都玩过游戏,以DNF里的职业剑魂为例,装饰器模式图示如下:
2、装饰器模式优点
装饰器相对于继承,装饰器模式灵活性更强,扩展性更强:
-
灵活性:装饰器模式将功能切分成一个个独立的装饰器,在运行期可以根据需要动态的 添加功能,甚至对添加的新功能进行自由的组合;
-
扩展性:当有新功能要添加的时候,只需要添加新的装饰器实现类,然后通过组合方式 添加这个新装饰器,无需修改已有代码,符合开闭原则;
3、装饰器模式使用举例
-
IO 中输入流和输出流的设计 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(“c://a.txt”)));
-
对网络爬虫的自定义增强,可增强的功能包括:多线程能力、缓存、自动生成报表、黑 白名单、random 触发等
三、装饰器在缓存模块的使用
==============
MyBatis 缓存模块是一个经典的使用装饰器实现的模块,类图如下:
-
Cache:Cache 接口是缓存模块的核 心接口,定义了缓存的基本操作;
-
PerpetualCache:在缓存模块中扮演 ConcreteComponent 角色,使用 HashMap 来实现 cache 的相关操作;
-
BlockingCache:阻塞版本的缓存装 饰器,保证只有一个线程到数据库去查 找指定的 key 对应的数据;BlockingCache 是阻塞版本的缓存装饰器,这个装饰器通过 ConcurrentHashMap 对锁的粒度 进行了控制,提高加锁后系统代码运行的效率(注:缓存雪崩的问题可以使用细粒度锁的方 式提升锁性能)
源码分析:
/**
-
Simple blocking decorator
-
Simple and inefficient version of EhCache’s BlockingCache decorator.
-
It sets a lock over a cache key when the element is not found in cache.
-
This way, other threads will wait until this element is filled instead of hitting the database.
-
阻塞版本的缓存装饰器,保证只有一个线程到数据库去查找指定的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();
}