,第二步:在mapper.xml配置文件中加入cache标签
,如果不需要自定义缓存,则不用指定type,此时会使用默认的缓存实现类org.apache.ibatis.cache.impl.PerpetualCache来实现二级缓存,它的底层就是用HashMap来实现的。在实际开发中,二级缓存还是用的比较多的,使用mybatis结合redis实现二级缓存非常方便,它也能适用于分布式场景。在阅读mybatis源码过程中,发现mybatis生成缓存类的时候,使用了装饰器模式和builder模式,结合的非常巧妙,故写此文章与各位分享。装饰器模式和builder模式不是本次主要讨论的主题,只作简要概述。
装饰器模式主要用于增强对象功能,
装饰器模式的核心在于给原始对象加装饰器是在构造方法中进行的,而原始对象是作为代理存在的,被内层装饰器装饰后的对象又作为外层装饰器的代理,使用时,外层装饰器执行方法时,核心方法由代理对象完成,同时可以加上装饰器的特有功能,例如日志记录。下面来看看mybatis中缓存类是如何使用装饰器模式的:
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();
ReadWriteLock getReadWriteLock();
}
这个接口规定了缓存类需要有的功能,缓存实现类PerpetualCache以及装饰器decorators都实现了这个接口,这个就规定了它们的类型都是一个Cache。前面说过 ***PerpetualCache***底层是由一个HashMap实现的,我们来看它的代码
public class PerpetualCache implements Cache {
private final String id;
private Map
它的功能比较单一,但是用于二级缓存也足够了。于是mybatis在此基础上,拓展了很多功能,都是装饰器实现,我们随便看一个org.apache.ibatis.cache.decorators.LoggingCache
,这个装饰器是记录缓存命中次数并打印日志。
public class LoggingCache implements Cache {
private final Log log;
private final Cache delegate;//被装饰的对象,也是执行方法的代理
protected int requests = 0;
protected int hits = 0;
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(getId());
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
requests++;
//核心方法由被装饰的类去执行,本类只是在此基础上作拓展
final Object value = delegate.getObject(key);
if (value != null) {
hits++;//记录缓存命中次数
}
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
private double getHitRatio() {
return (double) hits / (double) requests;
}
}
可以看到,每个装饰器都持有一个 delegate ,它就是 Cache,也就是被装饰的对象,在装饰器的构造方法里传入。装饰器有多少个都可以,被装饰过的对象就可以作为 delegate 再由下一个装饰器使用。那么mybatis是怎样构造这些装饰器的呢?接下来就看builder模式
builder 模式是一种对象构建模式,它跟工厂模式有些类似,工厂模式是提供一个工厂方法用于构造对象,而不需要用户手动在调用构造方法,而 builder 模式也是如此,但是 builder 模式的另一种变形是使用一个静态内部类来提供构造对象,这种方式可以完全对外部无影响地为对象增加builder模式的使用,在实际开发中用得比较多的,下面两种方式都会作介绍。
要给 PerpetualCache 加装饰器,就会有很多属性需要我们设置,一会可以从源码中看到,设置属性我们一般用set方法,而加装饰器的话,肯定要创建对象,前面说过,被装饰的对象是在装饰器的构造方法中设置进去的,使用传统方法就无可避免地要new很多对象,并且拓展性很差,后期如果需要再加一个装饰器的话,就要改动很多代码,于是这个时候builder模式就派上用场了。mybatis用一个***CacheBuilder***类完成以上工作(注意看注释):
public class CacheBuilder {
private final String id;
private Class extends Cache> implementation;
//所有的装饰器存放在一个数组中
private final List> decorators;
private Integer size;
private Long clearInterval;
private boolean readWrite;
private Properties properties;
private boolean blocking;
// CacheBuilder的构造方法,id是提前指定的
public CacheBuilder(String id) {
this.id = id;
this.decorators = new ArrayList<>();
}
public CacheBuilder implementation(Class extends Cache> implementation) {
this.implementation = implementation;
return this;
}
// 为缓存实现类增加装饰器,最后返回this,这样就可以使用链式编程,它也是
// builder模式的核心思想,在程序中体现就是
// new CacheBuilder(id).addDecorator(decorator1).addDecorator(decorator2)........
// 加入了这些装饰器,在后面构造对象的时候就可以加入进去了
public CacheBuilder addDecorator(Class extends Cache> decorator) {
if (decorator != null) {
this.decorators.add(decorator);
}
return this;
}
// builder模式给属性设置值的方式,不需要使用getter方法,并且返回this,方便链式编程
public CacheBuilder size(Integer size) {
this.size = size;
return this;
}
public CacheBuilder clearInterval(Long clearInterval) {
this.clearInterval = clearInterval;
return this;
}
public CacheBuilder readWrite(boolean readWrite) {
this.readWrite = readWrite;
return this;
}
public CacheBuilder blocking(boolean blocking) {
this.blocking = blocking;
return this;
}
public CacheBuilder properties(Properties properties) {
this.properties = properties;
return this;
}
// builder模式的最后一个方法,它会返回客户需要的对象。在这个方法中,将之前设置的属性全部加
// 入到对象中,同时也可进行属性校验。
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// 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);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
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);
}
}
private void setCacheProperties(Cache cache) {
if (properties != null) {
MetaObject metaCache = SystemMetaObject.forObject(cache);
for (Map.Entry
使用 CacheBuilder 创建对象,在程序中就是这样:
new CacheBuilder(id)
.addDecorator(decorator1)
.addDecorator(decorator2)
.size(size)
.clearInterval(clearInterval)
.readWrite(false)
.blocking(true)
.build();
当然设置这些属性是没有先后顺序的,通过这些操作,可以创建我们想要的对象,它的属性设置好了,装饰器也都装饰上了。
我们可以在开发过程中模仿mybatis的这种方式创建对象,新建一个XXXBuilder类来完成对象的创建工作,灵活控。然而,在工作中用的更多的是builder的另一种变形。举个例子:
public class Bean1 {
private String field1;
private int field2;
private boolean field3;
public Bean1() {
}
public Bean1(String field1) {
this.field1 = field1;
}
public Bean1(String field1, int field2) {
this(field1);
this.field2 = field2;
}
public Bean1(String field1, int field2, boolean field3) {
this(field1,field2);
this.field3 = field3;
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public int getField2() {
return field2;
}
public void setField2(int field2) {
this.field2 = field2;
}
public boolean isField3() {
return field3;
}
public void setField3(boolean field3) {
this.field3 = field3;
}
}
我们在工作中应该经常会写如上的代码,也就是一个javabean,以上我只是简单地例出了三个属性,当然在实际生产中,肯定不只三个属性,如果有很多个属性,构造方法就令人沮丧的了。所以这个时候,builder模式又派上用场了。 我们在一个javabean内部创建一个静态内部类buider,用于创建javabean的工作,而不需要显示地调用构造方法,这时不改动原先的任何代码,符合“对修改关闭,对拓展开放”的开闭原则,改进如下:
public class Bean1 {
private String field1;
private int field2;
private boolean field3;
public Bean1() {
}
public Bean1(String field1) {
this.field1 = field1;
}
public Bean1(String field1, int field2) {
this(field1);
this.field2 = field2;
}
public Bean1(String field1, int field2, boolean field3) {
this(field1,field2);
this.field3 = field3;
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public int getField2() {
return field2;
}
public void setField2(int field2) {
this.field2 = field2;
}
public boolean isField3() {
return field3;
}
public void setField3(boolean field3) {
this.field3 = field3;
}
/*-------------------------- 下面是新加代码,使用builder模式 ------------------*/
public static class builder {
private Bean1 bean = new Bean1();
public builder field1(String field1) {
bean.setField1(field1);
return this;
}
public builder field2(int field2) {
bean.setField2(field2);
return this;
}
public builder field3(boolean field3) {
bean.setField3(field3);
return this;
}
public Bean1 build() {
// 可以在此处加入一些必要的校验
if (bean.field1 == null || bean.field2 < 0)
throw new IllegalArgumentException("参数设置有误");
return bean;
}
}
}
在代码中加入builder这个内部类,自然不影响之前发布的代码,而现在创建对象就是这个样子Bean1 aaa = new builder().field1("aaa").field2(2).field3(true).build();
有没有觉得这种方式非常巧妙。现在你可以考虑在你的javabean中加入builder模式了。