前言
在学习设计模式时,对装饰器模式的定义还模糊不清,每次想在开发时使用该模式,总是不知道如何入手,从而转为使用其他模式去解决问题。
不过,就在这几天阅读mybatis的源码,看到了关于Cache缓存相关的使用方法,也算是大致明白如何使用了。
Cache类在mybatis中使用比较频繁,它除了基本的实现类外,还派生了很多装饰器类。关于装饰器模式的运用实例主要在本地缓存也就是二级缓存部分。
本文思路:
1. 装饰器定义和结构图
2. Cache装饰器在二级缓存中的形式
3. 缓存中使用装饰器的实际用例
装饰器模式
参考:装饰模式
定义
装饰器模式:动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
装饰器模式是添加额外的职责,而非对原有功能进行加工处理,这一点要注意。
装饰器的结构
Mybatis Cache源码
mybatis缓存装饰器的源码位置
简述Cache功能
Cache类是mybatis用于存储、查询结果的本地缓存,PerpetualCache是其基本的实现类。
下面是他们的基本结构
Cache源代码
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
Cache的基础子类PerpetualCache
其实就是封装了一个HashMap对象,所有的缓存操作都是基于该map对象。
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
// …………
}
Mybatis Cache装饰器类
Cache所有装饰器类
包decorators中,都是Cache的装饰器类,同时也是Cache的实现类,相当于这些装饰器类有两个身份:
- 原对象实现类
- 原对象装饰器类
有着这两重身份,各个装饰器之间就可以相互装饰。
注:作为Cache实现类时,必须要包装一个最终实现缓存操作的类,比如PerpetualCache。
Cache装饰器的作用
每个装饰器都有各自的独特行为,如下:
- BlockingCache:阻塞装饰器,当在缓存中找不到元素时,它在cache key上加锁以阻塞该get操作直到该元素进入缓存
- FifoCache:缓存元素超过一定限制后按队列形式先进先出
- LoggingCache:增加操作日志
- LruCache:缓存元素超过一定限制后删除最近最少使用的对象
- ScheduledCache:定时清理缓存
- SerializedCache:缓存可序列化
- SoftCache:软引用缓存(即将发生OOM时,清理缓存)
- WeakCache:弱引用缓存(每次GC时,都会清理缓存)
- SynchronizedCache:并发安全
- TransactionalCache:二级缓存事务缓冲区(作用暂时不明)
前面的定义描述了装饰器的作用:封装对象并为原对象绑定新的行为。
那么创建装饰器,就只需要两个步骤:
- 封装原对象到新的特殊对象
- 在特殊对象里添加新的行为
立足这两点来分析Cache的装饰器,会发现装饰器模式还是挺简单的。
Cache装饰器:FifoCache
前面说到,创建装饰器有两步:封装、添加行为。
以Cache的其中一个装饰器FifoCache为例:
- 通过构造器,将Cache的实现类进行封装。
- 为Cache添加行为:缓存元素超过一定限制后按队列形式先进先出
public class FifoCache implements Cache {
private final Cache delegate;
private final Deque<Object> keyList; //双端队列
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<>();
this.size = 1024; // 限制容量为1024
}
//…………
@Override
public void putObject(Object key, Object value) {
cycleKeyList(key);
delegate.putObject(key, value);
}
@Override
public Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyList.clear();
}
// 超过size便删除队首元素
private void cycleKeyList(Object key) {
keyList.addLast(key);
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
}
上面介绍了装饰器作用和具体的装饰器类,接下来会了解它在代码中的具体使用方法。
装饰器大显身手
接下来就到装饰器的使用部分,这一部分最能体现装饰器的特点。
不过这里有一点要注意,当各个装饰器层层包装时,要动态删除某个装饰器很困难,且各个装饰器的功能将会在很大程度上受到装饰顺序的影响。
CacheBuilder构建装饰器
Cache由CacheBuilder#bulid方法进行构建,在创建Cache时,mybatis添加了适当的装饰器为Cache添加特色行为。
该建造者的建造方法如下:
// CacheBuilder
public Cache build() {
setDefaultImplementations(); // 1
Cache cache = newBaseCacheInstance(implementation, id); // 2
setCacheProperties(cache); // 3
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache); // 4
setCacheProperties(cache);
}
cache = setStandardDecorators(cache); // 5
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache); // 6
}
return cache;
}
这里改建造者的参数来源于注解类@CacheNamespace
简单解释一下参数含义,对使用方法有兴趣可以自行查阅。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CacheNamespace {
// 设置缓存底层实现类
Class<? extends Cache> implementation() default PerpetualCache.class;
// 设置数据清理装饰器
Class<? extends Cache> eviction() default LruCache.class;
// 清理缓存间隔
long flushInterval() default 0;
// 缓存最大值
int size() default 1024;
// 是否启用读写缓存装饰器
boolean readWrite() default true;
// 是否启用阻塞装饰器
boolean blocking() default false;
// 预设缓存
Property[] properties() default {};
}
装饰器的使用过程:
1. 构造器在创建时,会默认将实现类设置为PerpetualCache,并在装饰器列表中加上LruCache
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
2. 实例化刚刚设置的PerpetualCache实现类
private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
try {
return cacheConstructor.newInstance(id);
} catch (Exception e) {
throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
}
}
3. 设置参数,如id,cache等,需要注意这些参数必须要有get、set方法
private void setCacheProperties(Cache cache) {
if (properties != null) {
MetaObject metaCache = SystemMetaObject.forObject(cache);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String name = (String) entry.getKey();
String value = (String) entry.getValue();
if (metaCache.hasSetter(name)) {
Class<?> type = metaCache.getSetterType(name);
if (String.class == type) {
metaCache.setValue(name, value);
} else if (int.class == type
// …………
// 初始化
if (InitializingObject.class.isAssignableFrom(cache.getClass())) {
try {
((InitializingObject) cache).initialize();
} catch (Exception e) {
throw new CacheException("Failed cache initialization for '"
+ cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
}
}
}
4. 使用装饰器来包装cache
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
}
5. 根据@CacheNamespace注解设置的属性值,来设置cache的默认参数,并选择装饰器进行装饰,可以看到日志和同步装饰器都是必选项。
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);
}
}
6. 在步骤3的操作中,是可以将cache指向一个新的Cache类的,因此需要确保这个新的类也会记录日志。
else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}