以上简单介绍了几种缓存配置方式,关于 MyBatis 缓存更多的知识,后面我会独立成文进行分析,这里就不深入说明了。下面我们来分析一下缓存配置的解析逻辑,如下:
private void cacheElement(XNode context) throws Exception { if (context != null) { // 获取各种属性 String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval"); Integer size = context.getIntAttribute("size"); boolean readWrite = !context.getBooleanAttribute("readOnly", false); boolean blocking = context.getBooleanAttribute("blocking", false); // 获取子节点配置 Properties props = context.getChildrenAsProperties(); // 构建缓存对象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }上面代码中,大段代码用来解析 <cache> 节点的属性和子节点,这些代码没什么好说的。缓存的构建逻辑封装在 BuilderAssistant 类的 useNewCache 方法中,下面我们来看一下该方法的逻辑。
// -☆- MapperBuilderAssistant public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass,Long flushInterval, Integer size,boolean readWrite,boolean blocking,Properties props) { // 使用建造模式构建缓存实例 Cache cache = new CacheBuilder(currentNamespace) .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); // 添加缓存到 Configuration 对象中 configuration.addCache(cache); // 设置 currentCache 遍历,即当前使用的缓存 currentCache = cache; return cache; }上面使用了建造模式构建 Cache 实例,Cache 实例的构建过程略为复杂,我们跟下去看看。
// -☆- CacheBuilder public Cache build() { // 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache) setDefaultImplementations(); // 通过反射创建缓存 Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); // 仅对内置缓存 PerpetualCache 应用装饰器 if (PerpetualCache.class.equals(cache.getClass())) { // 遍历装饰器集合,应用装饰器 for (Class<? extends Cache> decorator : decorators) { // 通过反射创建装饰器实例 cache = newCacheDecoratorInstance(decorator, cache); // 设置属性值到缓存实例中 setCacheProperties(cache); } // 应用标准的装饰器,比如 LoggingCache、SynchronizedCache cache = setStandardDecorators(cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { // 应用具有日志功能的缓存装饰器 cache = new LoggingCache(cache); } return cache; }上面的构建过程流程较为复杂,这里总结一下。如下:
设置默认的缓存类型及装饰器
应用装饰器到 PerpetualCache 对象上
遍历装饰器类型集合,并通过反射创建装饰器实例
将属性设置到实例中
应用一些标准的装饰器
对非 LoggingCache 类型的缓存应用 LoggingCache 装饰器
在以上4个步骤中,最后一步的逻辑很简单,无需多说。下面按顺序分析前3个步骤对应的逻辑,如下:
private void setDefaultImplementations() { if (implementation == null) { // 设置默认的缓存实现类 implementation = PerpetualCache.class; if (decorators.isEmpty()) { // 添加 LruCache 装饰器 decorators.add(LruCache.class); } } }以上逻辑比较简单,主要做的事情是在 implementation 为空的情况下,为它设置一个默认值。如果大家仔细看前面的方法,会发现 MyBatis 做了不少判空的操作。比如:
// 判空操作1,若用户未设置 cache 节点的 type 和 eviction 属性,这里设置默认值 PERPETUAL String type = context.getStringAttribute("type", "PERPETUAL"); String eviction = context.getStringAttribute("eviction", "LRU"); // 判空操作2,若 typeClass 或 evictionClass 为空,valueOrDefault 方法会为它们设置默认值 Cache cache = new CacheBuilder(currentNamespace) .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) // 省略部分代码 .build();既然前面已经做了两次判空操作,implementation 不可能为空,那么 setDefaultImplementations 方法似乎没有存在的必要了。其实不然,如果有人不按套路写代码。比如:
Cache cache = new CacheBuilder(currentNamespace) // 忘记设置 implementation .build();