1. 概述
本文接 《精尽 MyBatis 源码分析 —— MyBatis 初始化(一)之加载 mybatis-config》 一文,来分享 MyBatis 初始化的第二步,加载 Mapper 映射配置文件。而这个步骤的入口是 XMLMapperBuilder 。下面,我们一起来看看它的代码实现。
FROM 《Mybatis3.3.x技术内幕(八):Mybatis初始化流程(上)》
- 上图,就是 Mapper 映射配置文件的解析结果。
2. XMLMapperBuilder
org.apache.ibatis.builder.xml.XMLMapperBuilder
,继承 BaseBuilder 抽象类,Mapper XML 配置构建器,主要负责解析 Mapper 映射配置文件。
2.1 构造方法
// XMLMapperBuilder.java
/** * 基于 Java XPath 解析器 */ private final XPathParser parser; /** * Mapper 构造器助手 */ private final MapperBuilderAssistant builderAssistant; /** * 可被其他语句引用的可重用语句块的集合 * * 例如:<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql> */ private final Map<String, XNode> sqlFragments; /** * 资源引用的地址 */ private final String resource;
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) { this(inputStream, configuration, resource, sqlFragments); this.builderAssistant.setCurrentNamespace(namespace); }
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); }
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); // 创建 MapperBuilderAssistant 对象 this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; }
|
-
builderAssistant
属性,MapperBuilderAssistant 对象,是 XMLMapperBuilder 和 MapperAnnotationBuilder 的小助手,提供了一些公用的方法,例如创建 ParameterMap、MappedStatement 对象等等。关于 MapperBuilderAssistant 类,可见 「3. MapperBuilderAssistant」 。
2.2 parse
#parse()
方法,解析 Mapper XML 配置文件。代码如下:
// XMLMapperBuilder.java
public void parse() { // <1> 判断当前 Mapper 是否已经加载过 if (!configuration.isResourceLoaded(resource)) { // <2> 解析 `<mapper />` 节点 configurationElement(parser.evalNode("/mapper")); // <3> 标记该 Mapper 已经加载过 configuration.addLoadedResource(resource); // <4> 绑定 Mapper bindMapperForNamespace(); }
// <5> 解析待定的 <resultMap /> 节点 parsePendingResultMaps(); // <6> 解析待定的 <cache-ref /> 节点 parsePendingCacheRefs(); // <7> 解析待定的 SQL 语句的节点 parsePendingStatements(); }
|
-
<1>
处,调用 Configuration#isResourceLoaded(String resource)
方法,判断当前 Mapper 是否已经加载过。代码如下:
// Configuration.java
/** * 已加载资源( Resource )集合 */ protected final Set<String> loadedResources = new HashSet<>();
public boolean isResourceLoaded(String resource) { return loadedResources.contains(resource); }
|
-
<3>
处,调用 Configuration#addLoadedResource(String resource)
方法,标记该 Mapper 已经加载过。代码如下:
// Configuration.java
public void addLoadedResource(String resource) { loadedResources.add(resource); }
|
-
<2>
处,调用 #configurationElement(XNode context)
方法,解析 <mapper />
节点。详细解析,见 「2.3 configurationElement」 。
-
<4>
处,调用 #bindMapperForNamespace()
方法,绑定 Mapper 。详细解析,
-
<5>
、<6>
、<7>
处,解析对应的待定的节点。详细解析,见 「2.5 parsePendingXXX」 。
2.3 configurationElement
#configurationElement(XNode context)
方法,解析 <mapper />
节点。代码如下:
// XMLMapperBuilder.java
private void configurationElement(XNode context) { try { // <1> 获得 namespace 属性 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // <1> 设置 namespace 属性 builderAssistant.setCurrentNamespace(namespace); // <2> 解析 <cache-ref /> 节点 cacheRefElement(context.evalNode("cache-ref")); // <3> 解析 <cache /> 节点 cacheElement(context.evalNode("cache")); // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // <4> 解析 <resultMap /> 节点们 resultMapElements(context.evalNodes("/mapper/resultMap")); // <5> 解析 <sql /> 节点们 sqlElement(context.evalNodes("/mapper/sql")); // <6> 解析 <select /> <insert /> <update /> <delete /> 节点们 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); } }
|
2.3.1 cacheElement
#cacheRefElement(XNode context)
方法,解析 <cache-ref />
节点。代码如下:
// XMLMapperBuilder.java
private void cacheRefElement(XNode context) { if (context != null) { // <1> 获得指向的 namespace 名字,并添加到 configuration 的 cacheRefMap 中 configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); // <2> 创建 CacheRefResolver 对象,并执行解析 CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try { cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { // <3> 解析失败,添加到 configuration 的 incompleteCacheRefs 中 configuration.addIncompleteCacheRef(cacheRefResolver); } } }
|
-
示例如下:
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
|
-
<1>
处,获得指向的 namespace
名字,并调用 Configuration#addCacheRef(String namespace, String referencedNamespace)
方法,添加到 configuration
的 cacheRefMap
中。代码如下:
// Configuration.java
/** * A map holds cache-ref relationship. The key is the namespace that * references a cache bound to another namespace and the value is the * namespace which the actual cache is bound to. * * Cache 指向的映射 * * @see #addCacheRef(String, String) * @see org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheRefElement(XNode) */ protected final Map<String, String> cacheRefMap = new HashMap<>();
public void addCacheRef(String namespace, String referencedNamespace) { cacheRefMap.put(namespace, referencedNamespace); }
|
-
<2>
处,创建 CacheRefResolver 对象,并调用 CacheRefResolver#resolveCacheRef()
方法,执行解析。关于 CacheRefResolver ,在 「2.3.1.1 CacheRefResolver」 详细解析。
-
<3>
处,解析失败,因为此处指向的 Cache 对象可能未初始化,则先调用 Configuration#addIncompleteCacheRef(CacheRefResolver incompleteCacheRef)
方法,添加到 configuration
的 incompleteCacheRefs
中。代码如下:
// Configuration.java
/** * CacheRefResolver 集合 */ protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
public void addIncompleteCacheRef(CacheRefResolver incompleteCacheRef) { incompleteCacheRefs.add(incompleteCacheRef); }
|
2.3.1.1 CacheRefResolver
org.apache.ibatis.builder.CacheRefResolver
,Cache 指向解析器。代码如下:
// CacheRefResolver.java
public class CacheRefResolver {
private final MapperBuilderAssistant assistant; /** * Cache 指向的命名空间 */ private final String cacheRefNamespace;
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) { this.assistant = assistant; this.cacheRefNamespace = cacheRefNamespace; }
public Cache resolveCacheRef() { return assistant.useCacheRef(cacheRefNamespace); }
}
|
- 在
#resolveCacheRef()
方法中,会调用 MapperBuilderAssistant#useCacheRef(String namespace)
方法,获得指向的 Cache 对象。详细解析,见 「3.3 useCacheRef」 。
2.3.2 cacheElement
#cacheElement(XNode context)
方法,解析 cache />
标签。代码如下:
// XMLMapperBuilder.java
private void cacheElement(XNode context) throws Exception { if (context != null) { // <1> 获得负责存储的 Cache 实现类 String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); // <2> 获得负责过期的 Cache 实现类 String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); // <3> 获得 flushInterval、size、readWrite、blocking 属性 Long flushInterval = context.getLongAttribute("flushInterval"); Integer size = context.getIntAttribute("size"); boolean readWrite = !context.getBooleanAttribute("readOnly", false); boolean blocking = context.getBooleanAttribute("blocking", false); // <4> 获得 Properties 属性 Properties props = context.getChildrenAsProperties(); // <5> 创建 Cache 对象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
|
2.3.3 resultMapElements
老艿艿:开始高能,保持耐心。
整体流程如下:
FROM 《Mybatis3.3.x技术内幕(十):Mybatis初始化流程(下)》
#resultMapElements(List<XNode> list)
方法,解析 <resultMap />
节点们。代码如下:
// XMLMapperBuilder.java
// 解析 <resultMap /> 节点们 private void resultMapElements(List<XNode> list) throws Exception { // 遍历 <resultMap /> 节点们 for (XNode resultMapNode : list) { try { // 处理单个 <resultMap /> 节点 resultMapElement(resultMapNode); } catch (IncompleteElementException e) { // ignore, it will be retried } } }
// 解析 <resultMap /> 节点 private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList()); }
// 解析 <resultMap /> 节点 private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); // <1> 获得 id 属性 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); // <1> 获得 type 属性 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); // <1> 获得 extends 属性 String extend = resultMapNode.getStringAttribute("extends"); // <1> 获得 autoMapping 属性 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // <1> 解析 type 对应的类 Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; // <2> 创建 ResultMapping 集合 List<ResultMapping> resultMappings = new ArrayList<>(); resultMappings.addAll(additionalResultMappings); // <2> 遍历 <resultMap /> 的子节点 List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { // <2.1> 处理 <constructor /> 节点 if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); // <2.2> 处理 <discriminator /> 节点 } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); // <2.3> 处理其它节点 } else { List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } // <3> 创建 ResultMapResolver 对象,执行解析 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { // <4> 解析失败,添加到 configuration 中 configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
|
-
<resultMap />
标签的解析,是相对复杂的过程,情况比较多,所以胖友碰到不懂的,可以看看 《MyBatis 文档 —— Mapper XML 文件》 文档。
-
<1>
处,获得 id
、type
、extends
、autoMapping
属性,并解析 type
对应的类型。
-
<2>
处,创建 ResultMapping 集合,后遍历 <resultMap />
的子节点们,将每一个子节点解析成一个或多个 ResultMapping 对象,添加到集合中。即如下图所示: