mybatis源码解析之Configuration加载(五)
概述
前面几篇文章主要看了mybatis配置文件configuation.xml中<setting>,<environments>标签的加载,接下来看一下mapper标签的解析,先来看下标签的配置:
<mappers>
<!-- 方式一,mapper类和xml文件可以不再同一个目录下 -->
<!-- <mapper resource="mapper/newsMapper.xml" /> --> <!-- 方式二,必须保证mapper类和xml文件在同一个目录下 -->
<!--<package name="com.mybatis.read.dao" /> --> <!-- 方式三,必须保证mapper类和xml文件在同一个目录下 -->
<mapper class="com.mybatis.read.dao.NewsMapper" />
</mappers>
常用的主要就有上面三种方式,指定mapper的xml文件,指定package,指定mapper文件,我们来具体解析下。
解析
我们看下具体开始解析的代码:
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");
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) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
这段代码就是针对上面不同的配置进行解析加载,这里面还有一种url的情况,是通过url加载外部的资源,不常用。
我们先来看配置了package的情形,
第5行代码,获取包名,第6行代码调用configuration的addMappers方法,具体看下:
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
configuation中又调用了mapperRegistry的addMappers方法:
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
在看下addMappers方法:
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();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
上面这段代码主要就是利用ResolverUtil遍历package下面的所有class文件,看下find方法:
public ResolverUtil<T> find(Test test, String packageName) {
String path = getPackagePath(packageName); try {
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
} return this;
}
很明显,上面就是根据包名获取下面的class,然后调用addIfMatching方法:
protected void addIfMatching(Test test, String fqn) {
try {
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
} Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a " +
t.getClass().getName() + " with message: " + t.getMessage());
}
}
这个方法主要就是根据前面传过来的class,去加载这个类,注意这边用的类加载器是线程上下文类加载器,然后判断这个类的类型是不是Object类,是的话,就放入matches这个set中,都遍历完成之后,符合要求的class就在这个set中,怎么获取呢?看下getClass方法:
public Set<Class<? extends T>> getClasses() {
return matches;
}
这边调用getClass方法时就返回了这个Set,我们接着看mapperRegistry的addMappers方法,
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();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
按照上面的分析,第4行代码,我们已经得到的复合条件的class,下面就是遍历这些class,调用addMapper方法:
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 {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
这段代码首先判断这个class的类型是不是接口,不是则直接跳过,是的话,判断这个class对应的mapper文件是不是已经加载了,是的话,则抛出异常,否则看下第8行代码,将当前class类作为key,new一个MapperProxyFactory作为value放入knownMappers这个map中,这边这个MapperProxyFactory我们后面会详细介绍,看第12行代码,这边new了一个MapperAnnotationBuilder,看下第13行代码,调用了MapperAnnotationBuilder的parse方法:
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
这边我们只要关注第4行代码,第5行代码之后都是针对注解做的处理,具体看下:
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
这边首先再次确认xml文件有没有被加载过,没有的话继续加载,在当前目录下根据mapper的名字寻找xml文件,然后加载成流,之后交给XmlMapperBuilder处理,其实到这边,xml就已经加载完成了,后续都是处理一些使用了注解的东西,使用注解我们暂且不看。
我们再来看下方法三:
mapperClass不为空,走第三个if:
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");
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) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
第22行代码,根据名称加载mapper类,然后调用configuation的addMapper方法,这个我们上面已经分析过了,其实这种情况跟用package的很相似,可以说是一种特殊情况,package里面会有很多的mapper。
我们再看下方法一:
resource不为空:
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
这边跟上面那个部分一样的,我们下一篇文章就主要看下这个解析过程。
mybatis源码解析之Configuration加载(五)的更多相关文章
- mybatis源码解析之Configuration加载(四)
概述 上一篇文章,我们主要讲了datasource的相关内容,那么<environments>标签下的内容就看的差不多了,今天就来看一下在拿到transationManager和datas ...
- mybatis源码解析之Configuration加载(三)
概述 上一篇我们主要分析了下<environments>标签下面,transactionManager的配置,上问最后还有个遗留问题:就是在设置事物管理器的时候有个autocommit的变 ...
- mybatis源码解析之Configuration加载(二)
概述 上一篇我们讲了configuation.xml中几个标签的解析,例如<properties>,<typeAlises>,<settings>等,今天我们来介绍 ...
- mybatis源码解析之Configuration加载(一)
概要 上一篇,我们主要搭建了一个简单的环境,这边我们主要来分析下mybatis是如何来加载它的配置文件Configuration.xml的. 分析 public class App { public ...
- 【MyBatis源码分析】Configuration加载(下篇)
元素设置 继续MyBatis的Configuration加载源码分析: private void parseConfiguration(XNode root) { try { Properties s ...
- 【MyBatis源码分析】Configuration加载(上篇)
config.xml解析为org.w3c.dom.Document 本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分: ...
- webpack4.X源码解析之懒加载
本文针对Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一.准备工作 首先,init之后创建一个简单的webpack基本的配置,在src目录下创建两个js文件(一个主入口文件和一个非主 ...
- Spring源码解析-配置文件的加载
spring是一个很有名的java开源框架,作为一名javaer还是有必要了解spring的设计原理和机制,beans.core.context作为spring的三个核心组件.而三个组件中最重要的就是 ...
- Mybatis源码学习之资源加载(六)
类加载器简介 Java虚拟机中的类加载器(ClassLoader)负责加载来自文件系统.网络或其他来源的类文件.Java虚拟机中的类加载器默认使用的是双亲委派模式,如图所示,其中有三种默认使用的类加载 ...
随机推荐
- C# 模拟 HTTP POST请求
/// <summary> /// 用于以 POST 方式向目标地址提交表达数据 /// 使用 application/x-www-form-urlencoded 编码方式 /// 不支持 ...
- flutter测试页
import 'package:flutter/material.dart'; // 应用页面使用有状态Widget class AppScene extends StatefulWidget { @ ...
- [十二省联考2019]异或粽子 01trie
[十二省联考2019]异或粽子 01trie 链接 luogu 思路 首先求前k大的(xo[i]^xo[j])(i<j). 考场上只想到01trie,不怎么会写可持久,就写了n个01trie,和 ...
- Maven构建 SpringMVC+Spring+MyBatis 环境整合
目录 1. Maven 项目搭建 2. Maven 插件生成 MyBatis 代码 3. 待续 ... 开发环境 开发环境请尽量保持一致,不一致的情况可能存在问题. JDK 1.7 MyEclipse ...
- Sitecore8.2 Tracker.Current is not initialized错误
这是在访问前端的时候出现的错误...Tracker.Current 未初始化..并没更新修改什么,好端端的这样 而Siatecore后台可以正常访问进入 先查看日志--> 由此我判断可能是Mon ...
- _spellmod_aura_pct_on_stat
为玩家添加属性转换光环 `comment` 备注 `aura`光环ID `auraType1` 选择添加('治疗效果','法术强度','近战攻击强度','远程攻击强度','空') `statType ...
- script 修改 plist遇到的问题
一个sh脚本每次build的时候动态修改info.plist文件 达到动态更改版本号的目的 但是估计是因为缓存的缘故 每次只有clean之后再运行才会修改成功 看script执行的log 好像是先修改 ...
- 安装PyCharm开发工具
1.进入PyCharm官网 http://www.jetbrains.com/pycharm/ 2.点击现在下载 3.选择windows版本 4.打开安装程序 5.下一步,选择安装路径,安装 6.安装 ...
- 编译原理中DFA最小化
关于编译原理最小化的操作,专业术语请移步至:http://www.360doc.com/content/18/0601/21/11962419_758841916.shtml 这里只是记录一下个人的理 ...
- springboot访问数据库(MySql)
1.使用JDBC访问数据库:JDBC是用于在Java语言编程中与数据库连接的API <dependency> <groupId>org.springframework.boot ...