小视频软件开发,二级缓存的创建分析
小视频软件开发,二级缓存的创建分析
在Mybatis源码-加载映射文件与动态代理中已经知道,XMLMapperBuilder的configurationElement()方法会解析映射文件的内容并丰富到Configuration中,但在Mybatis源码-加载映射文件与动态代理中并未对解析映射文件的标签和标签进行说明,因此本小节将对这部分内容进行补充。configurationElement()方法如下所示。
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>标签
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>标签
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '"
+ resource + "'. Cause: " + e, e);
}
}
在configurationElement()方法中会先解析标签,然后再解析标签,因此在这里先进行一个推测:如果映射文件中同时存在和标签,那么标签配置的二级缓存会覆盖引用的二级缓存。下面先分析标签的解析,cacheElement()方法如下所示。
private void cacheElement(XNode context) {
if (context != null) {
// 获取<cache>标签的type属性值
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 获取<cache>标签的eviction属性值
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 获取<cache>标签的flushInterval属性值
Long flushInterval = context.getLongAttribute("flushInterval");
// 获取<cache>标签的size属性值
Integer size = context.getIntAttribute("size");
// 获取<cache>标签的readOnly属性值并取反
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 获取<cache>标签的blocking属性值
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
单步跟踪cacheElement()方法,每个属性解析出来的内容可以参照下图。
Cache的实际创建是在MapperBuilderAssistant的useNewCache()方法中,实现如下所示。
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.addCache(cache);
currentCache = cache;
return cache;
}
在MapperBuilderAssistant的useNewCache()方法中会先创建CacheBuilder,然后调用CacheBuilder的build()方法构建Cache。CacheBuilder类图如下所示。
CacheBuilder的构造函数如下所示。
public CacheBuilder(String id) {
this.id = id;
this.decorators = new ArrayList<>();
}
所以可以知道,CacheBuilder的id字段实际就是当前映射文件的namespace,其实到这里已经大致可以猜到,CacheBuilder构建出来的二级缓存Cache在Configuration中的唯一标识就是映射文件的namespace。此外,CacheBuilder中的implementation是PerpetualCache的Class对象,decorators集合中包含有LruCache的Class对象。下面看一下CacheBuilder的build()方法,如下所示。
public Cache build() {
setDefaultImplementations();
// 创建PerpetualCache,作为基础Cache对象
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
if (PerpetualCache.class.equals(cache.getClass())) {
// 为基础Cache对象添加缓存淘汰策略相关的装饰器
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;
}
CacheBuilder的build()方法首先会创建PerpetualCache对象,作为基础缓存对象,然后还会为基础缓存对象根据缓存淘汰策略添加对应的装饰器,比如标签中eviction属性值为LRU,那么对应的装饰器为LruCache,根据eviction属性值的不同,对应的装饰器就不同,下图是Mybatis为缓存淘汰策略提供的所有装饰器。
CacheBuilder的build()方法中,为PerpetualCache添加完缓存淘汰策略添装饰器后,还会继续添加标准装饰器,Mybatis中定义的标准装饰器有ScheduledCache,SerializedCache,LoggingCache,SynchronizedCache和BlockingCache,含义如下表所示。
如下是一个标签的示例。
那么生成的二级缓存对象如下所示。
整个装饰链如下图所示。
现在回到MapperBuilderAssistant的useNewCache()方法,构建好二级缓存对象之后,会将其添加到Configuration中,Configuration的addCache()方法如下所示。
public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}
这里就印证了前面的猜想,即二级缓存Cache在Configuration中的唯一标识就是映射文件的namespace。
现在再分析一下XMLMapperBuilder中的configurationElement()方法对标签的解析。cacheRefElement()方法如下所示。
private void cacheRefElement(XNode context) {
if (context != null) {
// 在Configuration的cacheRefMap中将当前映射文件命名空间与引用的映射文件命名空间建立映射关系
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// CacheRefResolver会将引用的映射文件的二级缓存从Configuration中获取出来并赋值给MapperBuilderAssistant的currentCache
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
cacheRefElement()方法会首先在Configuration的cacheRefMap中将当前映射文件命名空间与引用的映射文件命名空间建立映射关系,然后会通过CacheRefResolver将引用的映射文件的二级缓存从Configuration中获取出来并赋值给MapperBuilderAssistant的currentCache,currentCache这个字段后续会在MapperBuilderAssistant构建MappedStatement时传递给MappedStatement,以及如果映射文件中还存在标签,那么MapperBuilderAssistant会将标签配置的二级缓存重新赋值给currentCache以覆盖标签引用的二级缓存,所以映射文件中同时有标签和标签时,只有标签配置的二级缓存会生效。
以上就是小视频软件开发,二级缓存的创建分析, 更多内容欢迎关注之后的文章