在前一篇《Mybatis-启动流程》 中我们分析了Mybatis配置使用、 ConfigurationSqlSessionFactoryBuilder 的初始化流程,但是对于mapper的解析没有深入,主要是因为这部分的涉及的篇幅很长啊

在上篇有提到,通过不同的方式来指定mapper标签,会有两种builder来对mapper文件进行解析,分别是 :

  • XMLMapperBuilder
  • 解析xml类型的mapper
  • MapperAnnotationBuilder
  • 解析通过注解配置的mapper

快速导航

XMLMapperBuilder1. 判断是否已加载过2. 解析各个标签2.1 解析 缓存引用2.2 解析 缓存2.3 解析结果映射2.3.1 processConstructorElement2.3.1.1 buildResultMappingFromContext2.3.1.2 processNestedResultMappings2.3.2 processDiscriminatorElement2.3.3 buildResultMappingFromContext2.4 解析 sql 模板2.5 解析crud2.5.1XMLStatementBuilder2.5.1.1 在解析之前引入sql片段2.5.1.2 解析selectKey并将其删除2.5.1.3 解析SqlSource2.5.1.4 构建MappedStatement3. 尝试使用该类注册到mapper的注册表中4. 解析待定的xx节点MapperAnnotationBuilder1. 尝试加载并解析对应xml2. 标记为已加载的资源3. 用类的全限定名作为命名空间4. 解析缓存5. 解析缓存引用6. 解析mapper方法并为注册MappedStatement7. 解析待定的方法

XMLMapperBuilder

我们先从XMLMapperBuilder 开始:

FROM 《Mybatis3.3.x技术内幕(八):Mybatis初始化流程(上)》

XMLMapperBuilder的解析入口为parse方法:

public void parse() {
    // 判断是否已加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析各个标签
        configurationElement(parser.evalNode("/mapper"));
        // 添加到已加载的资源缓存中,避免重复解析
        configuration.addLoadedResource(resource);
        // 如果Namespace是某个类的全限定名,则使用该类注册到mapper的注册表中
        bindMapperForNamespace();
    }

    // 解析待定的 <resultMap /> 节点
    parsePendingResultMaps();
    // 解析待定的 <cache-ref /> 节点
    parsePendingCacheRefs();
    // 解析待定的 SQL 语句的节点
    parsePendingStatements();
}

1. 判断是否已加载过

protected final Set<String> loadedResources = new HashSet<String>();

public boolean isResourceLoaded(String resource) {
    return loadedResources.contains(resource);
}

2. 解析各个标签

private void configurationElement(XNode context) {
    try {
        // 获取并设置命名空间
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // 2.1 解析 缓存引用
        cacheRefElement(context.evalNode("cache-ref"));
        // 2.2 解析 缓存
        cacheElement(context.evalNode("cache"));
        // 解析参数Map,官方已经不建议使用
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 2.3 解析结果映射
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // 2.4 解析 sql 模板
        sqlElement(context.evalNodes("/mapper/sql"));
        // 2.5 解析crud
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        // 省略,抛出解析异常
    }
}

2.1 解析 缓存引用

<cache-ref />标签是用于引入其他命名空间的缓存,可以在多个命名空间中共享相同的缓存配置和实例。配置示例如下:

<cache-ref namespace="com.someone.application.data.SomeMapper"/>
private void cacheRefElement(XNode context) {
    if (context != null) {
        // 如果配置了 <cache-ref /> 先把这两个命名空间的缓存引用绑定到 configuration 的cacheRefMap中
        configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
        // 尝试解析cache ref
        CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
        try {
            cacheRefResolver.resolveCacheRef();
        } catch (IncompleteElementException e) {
            // 解析失败,可能是被引用的cache还没有初始化,则加入到 configuration 未初始化完的CacheRef缓存中
            configuration.addIncompleteCacheRef(cacheRefResolver);
        }
    }
}

2.2 解析 缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中配置<cache />标签,简单的示例如下:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>


<!-- 或者使用自定义缓存 -->
<cache type="com.domain.something.MyCustomCache"/>

这个配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

缓存标签的解析如下:

private void cacheElement(XNode context) throws Exception {
    if (context != null) {
        // 默认是永久缓存
        String type = context.getStringAttribute("type""PERPETUAL");
        // 解析缓存类型
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // 逐出策略,默认是LRU
        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);
    }
}

解析相关参数,并实例化该命名空间指定的二级缓存,再通过builderAssistant绑定到configuration上。

2.3 解析结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素。ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。 关于ResultMap 的用法可以参考官方文档,十分详细:戳我

private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
        try {
            // 循环解析
            resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
            // 忽略
        }
    }
}

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取id属性
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    // 获取实例化的类型,优先级为type 》 ofType 》 resultType 》 javaType
    // javaType和ofType都是用来指定对象类型的,但是javaType是用来指定pojo中属性或者类型,而ofType指定的是映射到list集合中的泛型的类型。
    String type = resultMapNode.getStringAttribute("type",
            resultMapNode.getStringAttribute("ofType",
                    resultMapNode.getStringAttribute("resultType",
                            resultMapNode.getStringAttribute("javaType"))));
    // 获取extends参数
    String extend = resultMapNode.getStringAttribute("extends");
    // 获取 autoMapping参数
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // 解析类型
    Class<?> typeClass = resolveClass(type);
    // 鉴别器
    Discriminator discriminator = null;
    // resultMappings
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    // 开始解析子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
        if ("constructor".equals(resultChild.getName())) {
            // 解析指定的构造器
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
            // 解析指定的鉴别器
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            // 解析映射字段关系并添加到resultMappings中
            List<ResultFlag> flags = new ArrayList<ResultFlag>();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    // 构建解析器进行解析
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
        return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
        // 添加到未完成的ResultMap缓存中
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

ResultMap的元素可以分为五大类,分别是:

  1. <id> 、 <result> : pojo属性与sql字段之间的映射关系
  2. <constructor> : 用于在实例化类时,注入结果到构造方法中
  3. <association> : 一个复杂类型的关联,比如被描述的属性是另一个pojo
  4. <collection> : 一个复杂类型的集合,他的子元素也可能是一个resultMap元素
  5. <discriminator> : 鉴别器,类似java中的switch语句,根据给定的字段来决定构建那个case指定的元素

Mybatis的resultMapElement方法把上述类型分为三种情况进行归类处理,分别未处理构造器的processConstructorElement,处理鉴别器的processDiscriminatorElement,与剩下的buildResultMappingFromContext(把collection和association标签作为一个内嵌的ResultMap来解析)。

2.3.1 processConstructorElement
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    List<XNode> argChildren = resultChild.getChildren();
    // 遍历提供的构造器参数
    for (XNode argChild : argChildren) {
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        // 添加构造器的标记
        flags.add(ResultFlag.CONSTRUCTOR);
        if ("idArg".equals(argChild.getName())) {
            // 添加id字段的标记
            flags.add(ResultFlag.ID);
        }
        // 把子标签构建为ResultMapping并加入到resultMappings列表中
        resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
}

主要是通过buildResultMappingFromContext解析构造器中配置的参数

2.3.1.1 buildResultMappingFromContext
/**
 * 作为ResultMap 的叶子节点元素的解析都会通过该方法解析
 * 包括:idArg / arg / id / result
 */

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    // 获取各种属性
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
        // 如果是构造器类型的就获取name参数(构造器参数名)
        property = context.getStringAttribute("name");
    } else {
        property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    // 嵌套的ResultMap,处理association、collection、case标签
    String nestedResultMap = context.getStringAttribute("resultMap",
            processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    // 解析类型
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 构建 ResultMapping
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

关于builderAssistant.buildResultMapping构建ResultMapping实体,主要是使用构建者模式,设置这些变量,解析javaType等参数的class,在做一些验证动作,然后完成ResultMapping实体的构建,这里不再展开分析。

2.3.1.2 processNestedResultMappings

解析嵌套的ResultMap,处理association、collection、case标签

private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
    // 如果当前节点是  association 或 collection 或 case 且没有 select属性,则把当前节点解析为 ResultMapping 并返回其id
    if ("association".equals(context.getName())
            || "collection".equals(context.getName())
            || "case".equals(context.getName())) {
        if (context.getStringAttribute("select") == null) {
            ResultMap resultMap = resultMapElement(context, resultMappings);
            return resultMap.getId();
        }
    }
    return null;
}
2.3.2 processDiscriminatorElement
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    // 获取属性
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    // 解析类型
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 添加鉴别器的case
    Map<String, String> discriminatorMap = new HashMap<String, String>();
    for (XNode caseChild : context.getChildren()) {
        String value = caseChild.getStringAttribute("value");
        String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
        discriminatorMap.put(value, resultMap);
    }
    // 构建鉴别器
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}

<discriminator>标签中获取需要鉴别的字段及类型等属性,把子元素<case>包装为HashMap,然后通过buildDiscriminator方法进行构建。

2.3.3 buildResultMappingFromContext

同 2.3.3.1 buildResultMappingFromContext

2.4 解析 sql 模板

private void sqlElement(List<XNode> list) throws Exception {
    if (configuration.getDatabaseId() != null) {
        // 配置了databaseIdProvider标签的话就只加载该数据库厂商标识的sql
        // 一般没有使用这个配置
        sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
}

private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
        String databaseId = context.getStringAttribute("databaseId");
        String id = context.getStringAttribute("id");
        id = builderAssistant.applyCurrentNamespace(id, false);
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            // 添加到sql碎片的缓存中
            sqlFragments.put(id, context);
        }
    }
}

private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
        if (!requiredDatabaseId.equals(databaseId)) {
            // 有配置databaseIdProvider,但是当前sql没有配置或者配置不一样
            return false;
        }
    } else {
        if (databaseId != null) {
            // 没有配置databaseIdProvider,但是当前sql指定了databaseId
            return false;
        }
        // 如果存在上一个具有不为null的databaseId的sql片段,则跳过此sql片段
        // TODO 如何加入的?
        if (this.sqlFragments.containsKey(id)) {
            XNode context = this.sqlFragments.get(id);
            if (context.getStringAttribute("databaseId") != null) {
                return false;
            }
        }
    }
    return true;
}

只是简单的把对应的节点信息缓存起来

2.5 解析crud

private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        // 构建一个XMLStatementBuilder 来解析
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}
2.5.1XMLStatementBuilder

构造器:

// XMLStatementBuilder.java

public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
    super(configuration);
    this.builderAssistant = builderAssistant;
    this.context = context;
    this.requiredDatabaseId = databaseId;
}

// BaseBuilder.java

// ============== 父类构造器 ==============
// 主要是获取configuration和typeAliasRegistry、typeHandlerRegistry对象
public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

解析方法:parseStatementNode

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    // 检查databaseId是否与配置的databaseIdProvider一致
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }
    // 获取参数
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    // 解析class
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    // 默认为预编译类型
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered"false);

    // 1. 在解析之前引入sql片段
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 2. 解析selectKey并将其删除。
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 3. 解析SQL(之前已解析并删除了<selectKey>和<include>)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    // 当useGeneratedKeys为true时,如果插入的表id以自增列为主键时,将会把该自增id返回。
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    // 4. 构建MappedStatement
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
2.5.1.1 在解析之前引入sql片段

<include /> 标签,替换成引用的 <sql />

public void applyIncludes(Node source) {
    // 获取一份属性拷贝
    Properties variablesContext = new Properties();
    Properties configurationVariables = configuration.getVariables();
    if (configurationVariables != null) {
        variablesContext.putAll(configurationVariables);
    }
    // 进行include
    applyIncludes(source, variablesContext, false);
}

private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
    if (source.getNodeName().equals("include")) {
        // 如果是 <include /> 标签,获得 <sql /> 对应的节点
        Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
        // 获得包含 <include /> 标签内的属性
        Properties toIncludeContext = getVariablesContext(source, variablesContext);
        // 递归调用 #applyIncludes(...) 方法,继续替换。注意,此处是 <sql /> 对应的节点
        applyIncludes(toInclude, toIncludeContext, true);
        if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
        }
        // 将 <include /> 节点替换成 <sql /> 节点
        source.getParentNode().replaceChild(toInclude, source);
        // 将 <sql /> 子节点添加到 <sql /> 节点前面
        while (toInclude.hasChildNodes()) {
            // 当子节点添加到其它节点下面后,这个子节点会不见了,相当于是“移动操作”
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
        }
        // 移除 <include /> 标签自身
        toInclude.getParentNode().removeChild(toInclude);
    } else if (source.getNodeType() == Node.ELEMENT_NODE) {
        if (included && !variablesContext.isEmpty()) {
            // 如果在处理 <include /> 标签中,则替换其上的属性,例如 <sql id="123" lang="${cpu}"> 的情况,lang 属性是可以被替换的
            NamedNodeMap attributes = source.getAttributes();
            for (int i = 0; i < attributes.getLength(); i++) {
                Node attr = attributes.item(i);
                attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
            }
        }
        // 遍历子节点,递归调用 #applyIncludes(...) 方法,继续替换
        NodeList children = source.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            applyIncludes(children.item(i), variablesContext, included);
        }
    } else if (included && source.getNodeType() == Node.TEXT_NODE
            && !variablesContext.isEmpty()) {
        // 如果在处理 <include /> 标签中,并且节点类型为 Node.TEXT_NODE ,并且变量非空
        // 则进行变量的替换,并修改原节点 source
        source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    }
}

这个替换逻辑为自递归的替换逻辑,里面涉及了三种流控,分别是:

  1. 处理include标签
  2. 处理元素类型的节点
  3. 处理文本节点

一个正常的处理流程应该是:先进入2处理元素类型的节点(如select标签),遍历子元素继续处理,如果发现include元素,再进入1进行解析,而3这是进行参数字段替换。

2.5.1.2 解析selectKey并将其删除

selectKey 元素描述如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">

可以在执行insert、update操作前或后执行selectkey指定的sql,比如在在插入前获取id等

private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
    // 解析selectKey节点
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
    // 移除 selectKey节点
    removeSelectKeyNodes(selectKeyNodes);
}

private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
    for (XNode nodeToHandle : list) {
        // 添加"!selectKey"后缀,标志这个为selectkey的id
        String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        String databaseId = nodeToHandle.getStringAttribute("databaseId");
        if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
            parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
        }
    }
}

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
    // 获得各种属性和对应的类
    String resultType = nodeToHandle.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
    String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order""AFTER"));

    // 默认配置
    boolean useCache = false;
    boolean resultOrdered = false;
    KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
    Integer fetchSize = null;
    Integer timeout = null;
    boolean flushCache = false;
    String parameterMap = null;
    String resultMap = null;
    ResultSetType resultSetTypeEnum = null;

    // 创建 SqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;

    // 构建 MappedStatement
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

    id = builderAssistant.applyCurrentNamespace(id, false);
    // 构建一个MappedStatement
    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
    // 注册到configuration中的KeyGenerator,类型是SelectKeyGenerator
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

// 移除select key节点
private void removeSelectKeyNodes(List<XNode> selectKeyNodes) {
    for (XNode nodeToHandle : selectKeyNodes) {
        nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
    }
}

作用是生成一个已父id + “!selectKey”为新的id,然后生成SelectKeyGenerator类型的MappedStatement,然后注册到configuration中。

关于SqlSourceMappedStatement在后续分析

2.5.1.3 解析SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

在说SqlSource之前我们先来聊聊LanguageDriverLanguageDriver接口定义了创建ParameterHandlerSqlSource的方法。在Configuration初始化的时候就已经注册到languageRegistry中了:

public Configuration() {
        // 省略非必要代码
        typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
        // XMLLanguageDriver是默认的语言驱动
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
    }

LanguageDriver接口定义如下:

public interface LanguageDriver {

  /**
   * 创建 ParameterHandler 对象
   */

  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  /**
   * 创建 SqlSource 对象,从 Mapper XML 配置的 Statement 标签中,即 <select /> 等。
   */

  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

  /**
   * 创建 SqlSource 对象,从方法注解配置,即 @Select 等。
   */

  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

XMLLanguageDriver 是XML 语言驱动实现类 ,其子类RawLanguageDriverRawSqlSource 语言驱动器实现类,确保创建的 SqlSourceRawSqlSource 类。

// XMLLanguageDriver.java 

public class XMLLanguageDriver implements LanguageDriver {

    /**
     * 创建 ParameterHandler 对象
     */

    @Override
    public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        // 创建默认的参数处理器
        return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
    }

    /**
     * 创建 SqlSource 对象,从 Mapper XML 配置的 Statement 标签中,即 <select /> 等。
     */

    @Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        // 使用XMLScriptBuilder解析sql
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        return builder.parseScriptNode();
    }

    /**
     * 创建 SqlSource 对象,从方法注解配置,即 @Select 等。
     */

    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        // issue #3
        if (script.startsWith("<script>")) {
            // 解析script标签
            XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
            // 调用解析xml的方法来构建SqlSource
            return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
        } else {
            // issue #127
            // 非xml结构,直接使用TextSqlNode来承载,然后创建对应的SqlSource实例
            script = PropertyParser.parse(script, configuration.getVariables());
            TextSqlNode textSqlNode = new TextSqlNode(script);
            if (textSqlNode.isDynamic()) {
                return new DynamicSqlSource(configuration, textSqlNode);
            } else {
                return new RawSqlSource(configuration, script, parameterType);
            }
        }
    }
}

关于注解中使用script标签,可以达到动态SQL的效果,比如:

@Select({"<script>",
    "SELECT * FROM user",
    "<where>",
    "<when test='name!=null'>",
    "and name = #{name}",
    "</when>",
    "</where>",
    "</script>"})
// RawLanguageDriver.java

public class RawLanguageDriver extends XMLLanguageDriver {

    @Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        // 调用父类,创建 SqlSource 对象
        SqlSource source = super.createSqlSource(configuration, script, parameterType);
        // 校验创建的是 RawSqlSource 对象
        checkIsNotDynamic(source);
        return source;
    }

    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        // 调用父类,创建 SqlSource 对象
        SqlSource source = super.createSqlSource(configuration, script, parameterType);
        // 校验创建的是 RawSqlSource 对象
        checkIsNotDynamic(source);
        return source;
    }

    /**
     * 校验是 RawSqlSource 对象
     */

    private void checkIsNotDynamic(SqlSource source) {
        if (!RawSqlSource.class.equals(source.getClass())) {
            // 抛出异常
        }
    }

}

都是调用父类的创建方法,只是最后检查一下创建的SqlSource类型

那么我们继续LanguageDrivercreateSqlSource方法:

/**
 * 创建 SqlSource 对象,从 Mapper XML 配置的 Statement 标签中,即 <select /> 等。
 */

@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    // 使用XMLScriptBuilder解析sql
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
}

我们只分析这个方法就行,因为如果使用注解提供包含<script>标签的文本,还是会调用到本方法来构建SqlSource,如果没有包含脚本,则直接使用一个TextSqlNode来构建一个SqlSource

这里提及的TextSqlNode的下面的解析到。

构建XMLScriptBuilder进行解析:

public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
}


private void initNodeHandlerMap() {
    // 注册标签解析器
    nodeHandlerMap.put("trim"new TrimHandler());
    nodeHandlerMap.put("where"new WhereHandler());
    nodeHandlerMap.put("set"new SetHandler());
    nodeHandlerMap.put("foreach"new ForEachHandler());
    nodeHandlerMap.put("if"new IfHandler());
    nodeHandlerMap.put("choose"new ChooseHandler());
    nodeHandlerMap.put("when"new IfHandler());
    nodeHandlerMap.put("otherwise"new OtherwiseHandler());
    nodeHandlerMap.put("bind"new BindHandler());
}

// 解析方法
public SqlSource parseScriptNode() {
    // 解析动态标签为MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    // 根据是否为动态sql,创建对应的sqlSource实例
    if (isDynamic) {
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

// 解析动态标签
protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    // 遍历子节点
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        // 获取一份拷贝
        XNode child = node.newXNode(children.item(i));
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            // 如果是CDATA或者TEXT_NODE
            String data = child.getStringBody("");
            // 构建为TextSqlNode
            TextSqlNode textSqlNode = new TextSqlNode(data);
            // 解析data是否包含变量
            if (textSqlNode.isDynamic()) {
                // 是的话添加到contents中,并设置isDynamic为true
                contents.add(textSqlNode);
                isDynamic = true;
            } else {
                // 否则构建一个静态的TextSqlNode添加到contents中
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            // 解析自定义标签
            String nodeName = child.getNode().getNodeName();
            // 获取标签处理器
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            // 解析标签,并加入到contents中,比如是<where /> 标签就会构建一个WhereSqlNode的节点
            handler.handleNode(child, contents);
            isDynamic = true;
        }
    }
    return new MixedSqlNode(contents);
}

上面的代码很多,但是不复杂,经过一系列操,把sql转换成一系列的sqlNode节点,然后把这些sqlNode组合为MixedSqlNode返回。

关于sqlNode这里简单看看,不做展开:

public class MixedSqlNode implements SqlNode {
    private final List<SqlNode> contents;

    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 调用sqlNodde 的 apply方法,动态标签的解析最终都会在apply中完成,并输出sql
        for (SqlNode sqlNode : contents) {
            sqlNode.apply(context);
        }
        return true;
    }
}
2.5.1.4 构建MappedStatement
// 构建MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
// MapperBuilderAssistant.java

public MappedStatement addMappedStatement(
        String id,
        SqlSource sqlSource,
        StatementType statementType,
        SqlCommandType sqlCommandType,
        Integer fetchSize,
        Integer timeout,
        String parameterMap,
        Class<?> parameterType,
        String resultMap,
        Class<?> resultType,
        ResultSetType resultSetType,
        boolean flushCache,
        boolean useCache,
        boolean resultOrdered,
        KeyGenerator keyGenerator,
        String keyProperty,
        String keyColumn,
        String databaseId,
        LanguageDriver lang,
        String resultSets)
 
{

    if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    // sql id
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 构建MappedStatement的建造者
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
            .resource(resource)
            .fetchSize(fetchSize)
            .timeout(timeout)
            .statementType(statementType)
            .keyGenerator(keyGenerator)
            .keyProperty(keyProperty)
            .keyColumn(keyColumn)
            .databaseId(databaseId)
            .lang(lang)
            .resultOrdered(resultOrdered)
            .resultSets(resultSets)
            .resultMaps(getStatementResultMaps(resultMap, resultType, id))
            .resultSetType(resultSetType)
            .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
            .useCache(valueOrDefault(useCache, isSelect))
            .cache(currentCache);
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
    }
    // 进行构建,只是对MappedStatement做一些必要的校验,并把resultMap设置为不可修改
    MappedStatement statement = statementBuilder.build();
    // 添加到MappedStatement
    configuration.addMappedStatement(statement);
    return statement;
}

// Configuration.java

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
}

好,到此我们知道Mybatis是如果通过XMLMapperBuilder来解析xml文件并且注册到mappedStatements,关于mappedStatement的时候会在后续的章节分析。

回头再看看我们本篇解析方法的入口:

public void parse() {
    // 判断是否已加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析各个标签
        configurationElement(parser.evalNode("/mapper"));
        // 添加到已加载的资源缓存中,避免重复解析
        configuration.addLoadedResource(resource);
        // 如果Namespace是某个类的全限定名,则使用该类注册到mapper的注册表中
        bindMapperForNamespace();
    }

    // 解析待定的 <resultMap /> 节点
    parsePendingResultMaps();
    // 解析待定的 <cache-ref /> 节点
    parsePendingCacheRefs();
    // 解析待定的 SQL 语句的节点
    parsePendingStatements();
}

我们目前为止直接解析了configurationElement

3. 尝试使用该类注册到mapper的注册表中

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;
        try {
            // 尝试获取Class
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
        }
        if (boundType != null) {
            // 如果能获取到,且没有注册过,就进行注册
            if (!configuration.hasMapper(boundType)) {
                configuration.addLoadedResource("namespace:" + namespace);
                configuration.addMapper(boundType);
            }
        }
    }
}

这是为了使用该类的权限名+方法名也能找到对应的sql

4. 解析待定的xx节点

private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
        Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
        while (iter.hasNext()) {
            try {
                iter.next().resolve();
                iter.remove();
            } catch (IncompleteElementException e) {
                // ResultMap is still missing a resource...
            }
        }
    }
}

private void parsePendingCacheRefs() {
    Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
    synchronized (incompleteCacheRefs) {
        Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
        while (iter.hasNext()) {
            try {
                iter.next().resolveCacheRef();
                iter.remove();
            } catch (IncompleteElementException e) {
                // Cache ref is still missing a resource...
            }
        }
    }
}

private void parsePendingStatements() {
    Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
    synchronized (incompleteStatements) {
        Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
        while (iter.hasNext()) {
            try {
                iter.next().parseStatementNode();
                iter.remove();
            } catch (IncompleteElementException e) {
                // Statement is still missing a resource...
            }
        }
    }
}

套路都是一样的,就是把之前没有解析完的循环解析,知道解析完成

MapperAnnotationBuilder

回过头我们再看看mapperElement的方法:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                // 扫描package下的所有接口,构建MapperAnnotationBuilder进行解析
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    // 省略,通过resource指定xml文件的位置,使用XMLMapperBuilder解析
                } else if (resource == null && url != null && mapperClass == null) {
                    // 省略,通过url指定xml文件的位置,使用XMLMapperBuilder解析
                } else if (resource == null && url == null && mapperClass != null) {
                    // 加载指定的class,通过MapperAnnotationBuilder进行解析
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    // 省略抛出错误
                }
            }
        }
    }
}

剩下的就是基于注解的注册mapper,直接调用了configurationaddMapper方法。那么,走起~

// 基于package扫描
public void addMappers(String packageName) {
    //
    addMappers(packageName, Object.class);
}

public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 递归解析每个class
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        // 只解析接口类型
        if (hasMapper(type)) {
            // 已解析过的抛出错误
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // 加入到已知的mapper里
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            // 构建MapperAnnotationBuilder进行解析
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                // 如果出现异常,则移除该类型
                knownMappers.remove(type);
            }
        }
    }
}

构建MapperAnnotationBuilder

private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();

private final Configuration configuration;
private final MapperBuilderAssistant assistant;
private final Class<?> type;

public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
    String resource = type.getName().replace('.''/') + ".java (best guess)";
    // 构建辅助类
    this.assistant = new MapperBuilderAssistant(configuration, resource);
    this.configuration = configuration;
    this.type = type;
    // 注册注解
    sqlAnnotationTypes.add(Select.class);
    sqlAnnotationTypes.add(Insert.class);
    sqlAnnotationTypes.add(Update.class);
    sqlAnnotationTypes.add(Delete.class);
    // 注册Provider注解
    sqlProviderAnnotationTypes.add(SelectProvider.class);
    sqlProviderAnnotationTypes.add(InsertProvider.class);
    sqlProviderAnnotationTypes.add(UpdateProvider.class);
    sqlProviderAnnotationTypes.add(DeleteProvider.class);
}

创建了MapperBuilderAssistant来辅助构建,然后主要相关的注解。

public void parse() {
    // 判断当前 Mapper 接口是否应加载过。
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 1. 尝试加载并解析对应xml
        loadXmlResource();
        // 2. 标记为已加载的资源
        configuration.addLoadedResource(resource);
        // 3. 用类的全限定名作为命名空间
        assistant.setCurrentNamespace(type.getName());
        // 4. 解析缓存
        parseCache();
        // 5. 解析缓存引用
        parseCacheRef();
        // 6. 解析mapper方法
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            try {
                // issue #237
                if (!method.isBridge()) {
                    // 只解析非桥接方法,桥接方法是编译器为了兼容注解而自动生成的方法
                    // 解析并注册MappedStatement
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    // 7. 解析待定的方法
    parsePendingMethods();
}

基于注解的解析和基于xml的解析其实是很类似的

关于java桥接方法不清楚的可以看看这篇博客:戳我

1. 尝试加载并解析对应xml
private void loadXmlResource() {
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        // 如果对应的xml文件已经解析过,并且xml文件的命名空间是一个类名,会生产一个【"namespace:"+类全限定名】的资源绑定到已加载的资源中
        String xmlResource = type.getName().replace('.''/') + ".xml";
        InputStream inputStream = null;
        try {
            // 尝试加载xml
            inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e) {
            // 忽略
        }
        if (inputStream != null) {
            // 如果能加载出来,则使用XMLMapperBuilder进行解析绑定
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
            xmlParser.parse();
        }
    }
}
2. 标记为已加载的资源
public void addLoadedResource(String resource) {
    loadedResources.add(resource);
}
3. 用类的全限定名作为命名空间
public void setCurrentNamespace(String currentNamespace) {
    if (currentNamespace == null) {
        // 省略,抛出异常
    }

    if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
        // 省略,抛出异常
    }

    this.currentNamespace = currentNamespace;
}
4. 解析缓存
private void parseCache() {
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    if (cacheDomain != null) {
        // 获取配置并构建缓存
        Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
        Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
        Properties props = convertToProperties(cacheDomain.properties());
        assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
}
5. 解析缓存引用
private void parseCacheRef() {
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    if (cacheDomainRef != null) {
        // 获取引用的类或者命名空间
        Class<?> refType = cacheDomainRef.value();
        String refName = cacheDomainRef.name();
        // 两者不能同时为空或同时设定
        if (refType == void.class && refName.isEmpty()) {
            // 省略,抛出异常
        }
        if (refType != void.class && !refName.isEmpty()) {
            // 省略,抛出异常
        }
        String namespace = (refType != void.class) ? refType.getName() : refName;
        // 设置二级缓存
        assistant.useCacheRef(namespace);
    }
}

如果@CacheNamespace和@CacheNamespaceRef同时配置,最终会使用@CacheNamespaceRef配置的二级缓存

6. 解析mapper方法并为注册MappedStatement

遍历接口的方法,进行parseStatement方法的调用

void parseStatement(Method method) {
    // 获取参数类型的Class
    Class<?> parameterTypeClass = getParameterType(method);
    // 获取语言驱动
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 构建 SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
        // 获取配置,包括是否使用缓存,是否刷新缓存等
        Options options = method.getAnnotation(Options.class);
        // sql id
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = !isSelect;
        boolean useCache = isSelect;

        KeyGenerator keyGenerator;
        String keyProperty = "id";
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // first check for SelectKey annotation - that overrides everything else
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {
                // 如果有配置selectKey,优先从selectKey中解析
                keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            } else if (options == null) {
                keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            } else {
                keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
                keyProperty = options.keyProperty();
                keyColumn = options.keyColumn();
            }
        } else {
            keyGenerator = NoKeyGenerator.INSTANCE;
        }

        if (options != null) {
            // 配置其他属性
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
                flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
                flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null//issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            resultSetType = options.resultSetType();
        }

        // 配置 ResultMap
        String resultMapId = null;
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
            String[] resultMaps = resultMapAnnotation.value();
            StringBuilder sb = new StringBuilder();
            for (String resultMap : resultMaps) {
                if (sb.length() > 0) {
                    sb.append(",");
                }
                sb.append(resultMap);
            }
            resultMapId = sb.toString();
        } else if (isSelect) {
            resultMapId = parseResultMap(method);
        }
        // 注册为MappedStatement
        assistant.addMappedStatement(
                mappedStatementId,
                sqlSource,
                statementType,
                sqlCommandType,
                fetchSize,
                timeout,
                // ParameterMapID
                null,
                parameterTypeClass,
                resultMapId,
                getReturnType(method),
                resultSetType,
                flushCache,
                useCache,
                // TODO gcode issue #577
                false,
                keyGenerator,
                keyProperty,
                keyColumn,
                // DatabaseID
                null,
                languageDriver,
                // ResultSets
                options != null ? nullOrEmpty(options.resultSets()) : null);
    }
}

代码虽长,但是套路都是一样的,只不过是把xml需要解析的内容,从注解或者方法参数,类属性中提取而已

7. 解析待定的方法
private void parsePendingMethods() {
    // 获得 MethodResolver 集合,并遍历进行处理
    Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
    synchronized (incompleteMethods) {
        Iterator<MethodResolver> iter = incompleteMethods.iterator();
        while (iter.hasNext()) {
            try {
                // 执行解析
                iter.next().resolve();
                iter.remove();
            } catch (IncompleteElementException e) {
                // This method is still missing a resource
            }
        }
    }
}