Mybatis源码分析(第二章)------映射文件的解析(1)

2.1 映射文件解析过程分析

MyBatis 的配置文件由 XMLConfigBuilder 的 parseConfiguration 进行解析,该方法依次解析了 <properties>、<settings>、<typeAliases> 等节点。至于 <mappers> 节点,parseConfiguration 则是在方法的结尾对其进行了解析。该部分的解析逻辑封装在 mapperElement 方法中:

 1 private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3         for (XNode child : parent.getChildren()) {
 4             if ("package".equals(child.getName())) {
 5                 // 获取 <package> 节点中的 name 属性
 6                 String mapperPackage = child.getStringAttribute("name");
 7                 // 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置
 8                 configuration.addMappers(mapperPackage);
 9             } else {
10                 // 获取 resource/url/class 等属性
11                 String resource = child.getStringAttribute("resource");
12                 String url = child.getStringAttribute("url");
13                 String mapperClass = child.getStringAttribute("class");
14 
15                 // resource 不为空,且其他两者为空,则从指定路径中加载配置
16                 if (resource != null && url == null && mapperClass == null) {
17                     ErrorContext.instance().resource(resource);
18                     InputStream inputStream = Resources.getResourceAsStream(resource);
19                     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
20                     // 解析映射文件
21                     mapperParser.parse();
22 
23                 // url 不为空,且其他两者为空,则通过 url 加载配置
24                 } else if (resource == null && url != null && mapperClass == null) {
25                     ErrorContext.instance().resource(url);
26                     InputStream inputStream = Resources.getUrlAsStream(url);
27                     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
28                     // 解析映射文件
29                     mapperParser.parse();
30 
31                 // mapperClass 不为空,且其他两者为空,则通过 mapperClass 解析映射配置
32                 } else if (resource == null && url == null && mapperClass != null) {
33                     Class<?> mapperInterface = Resources.classForName(mapperClass);
34                     configuration.addMapper(mapperInterface);
35 
36                 // 以上条件不满足,则抛出异常
37                 } else {
38                     throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
39                 }
40             }
41         }
42     }
43 }

上面的代码主要逻辑是遍历 mappers 的子节点,并根据节点属性值判断通过什么方式加载映射文件或映射信息。这里,我把配置在注解中的内容称为映射信息,以 XML 为载体的配置称为映射文件。在 MyBatis 中,共有四种加载映射文件或信息的方式。第一种是从文件系统中加载映射文件;第二种是通过 URL 的方式加载和解析映射文件;第三种是通过 mapper 接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中。最后一种是通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。

限于 Java 注解的表达力和灵活性,通过注解的方式并不能完全发挥 MyBatis 的能力。所以,对于一些较为复杂的配置信息,我们还是应该通过 XML 的方式进行配置。

接下来的部分都是重点分析基于XML映射文件的解析过程,下面先分析映射文件解析入口:

 1 public void parse() {
 2     // 检测映射文件是否已经被解析过
 3     if (!configuration.isResourceLoaded(resource)) {
 4         // 解析 mapper 节点
 5         configurationElement(parser.evalNode("/mapper"));
 6         // 添加资源路径到“已解析资源集合”中
 7         configuration.addLoadedResource(resource);
 8         // 通过命名空间绑定 Mapper 接口
 9         bindMapperForNamespace();
10     }
11 
12     // 处理未完成解析的节点
13     parsePendingResultMaps();
14     parsePendingCacheRefs();
15     parsePendingStatements();
16 }

如上,映射文件解析入口逻辑包含三个核心操作,分别如下:

  1. 解析 mapper 节点
  2. 通过命名空间绑定 Mapper 接口
  3. 处理未完成解析的节点

这三个操作对应的逻辑,我将会在随后的章节中依次进行分析。下面,先来分析第一个操作对应的逻辑。

2.2 解析映射文件

配置为文件中每种配置中的每种节点的解析逻辑都封装在了相应的方法中,这些方法由 XMLMapperBuilder 类的 configurationElement 方法统一调用。该方法的逻辑如下:

 1 private void configurationElement(XNode context) {
 2     try {
 3         // 获取 mapper 命名空间
 4         String namespace = context.getStringAttribute("namespace");
 5         if (namespace == null || namespace.equals("")) {
 6             throw new BuilderException("Mapper's namespace cannot be empty");
 7         }
 8 
 9         // 设置命名空间到 builderAssistant 中
10         builderAssistant.setCurrentNamespace(namespace);
11 
12         // 解析 <cache-ref> 节点
13         cacheRefElement(context.evalNode("cache-ref"));
14 
15         // 解析 <cache> 节点
16         cacheElement(context.evalNode("cache"));
17 
18         // 已废弃配置,这里不做分析
19         parameterMapElement(context.evalNodes("/mapper/parameterMap"));
20 
21         // 解析 <resultMap> 节点
22         resultMapElements(context.evalNodes("/mapper/resultMap"));
23 
24         // 解析 <sql> 节点
25         sqlElement(context.evalNodes("/mapper/sql"));
26 
27         // 解析 <select>、...、<delete> 等节点
28         buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
29     } catch (Exception e) {
30         throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
31     }
32 }

  

待更新。。。

猜你喜欢

转载自www.cnblogs.com/Emiyaa/p/11325239.html