一,加载Mapper配置文件
1.1mappers有四种配置方式:
方式一:
<mappers>
<package name="被代理对象(接口)的所在包"/>
</mappers>
注意:注册指定包下的所有mapper接口;此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个包中。
方式二:
<mappers>
<mapper resource="mapper/myuser.xml"/>
</mappers>
注意:resource是以项目路径开始的
方式三:
<mappers>
<mapper url="http://xxx/xxx/xx.xml"/>
</mappers>
注意:url是远程配置文件
方式四:
<mappers>
<mapper class="被代理对象(接口)的全路径名"/>
</mappers>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个包中
mybatis是通过this.mapperElement(root.evalNode("mappers"));
方法,至于为什么调用该方法,可以看上一篇文章,介绍解析mybatis的核心配置文件的文章。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 获取mappers标签的子标签
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
// 循环遍历
XNode child = (XNode)var2.next();
String resource;
// 方式一:包扫描的方式
if ("package".equals(child.getName())) {
// 如果mappers标签的子标签是package,获取package的name属性。
resource = child.getStringAttribute("name");
// 通过name值,获取name下的所有mapper配置文件
this.configuration.addMappers(resource);
} else {
// 获取resource、url和class对应属性值
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
//方式二:通过resource属性加载文件
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//方式三:通过url属性加载文件
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
// 方式四:通过class属性加载文件
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
方式一包扫描的方式,查看this.configuration.addMappers(resource);
方法:
public void addMappers(String packageName) {
this.mapperRegistry.addMappers(packageName);
}
public void addMappers(String packageName) {
this.addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
// 又是创建ResolverUtil工具类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
// 根据packageName获取,该包下的所有Class
resolverUtil.find(new IsA(superType), packageName);
// 遍历Class
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
Iterator var5 = mapperSet.iterator();
while(var5.hasNext()) {
Class<?> mapperClass = (Class)var5.next();
// 方式四通过class属性加载文件
this.addMapper(mapperClass);
}
}
方式四通过class属性加载文件,查看this.configuration.addMapper(mapperInterface);
和上面的this.addMapper(mapperClass);
方法最终都是调用如下方法:
public <T> void addMapper(Class<T> type) {
// 判断Class是否是接口
if (type.isInterface()) {
// 配置文件是否正在加载
if (this.hasMapper(type)) {
// 正在加载过就抛出异常
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 加载之前,存入this.knownMappers中
this.knownMappers.put(type, new MapperProxyFactory(type));
// 创建MapperAnnotationBuilder对象,
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
// 解析
parser.parse();
loadCompleted = true;
} finally {
// 完成解析
if (!loadCompleted) {
// 移出
this.knownMappers.remove(type);
}
}
}
}
配置文件是否加载过,查看this.hasMapper(type)
方法:
public <T> boolean hasMapper(Class<T> type) {
// 加载之前,存入this.knownMappers中
return this.knownMappers.containsKey(type);
}
创建MapperAnnotationBuilder对象
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;
}
解析查看parser.parse();
方法:
public void parse() {
String resource = this.type.toString();
// 判断configuration的this.loadedResources集合中是否包含resource
if (!this.configuration.isResourceLoaded(resource)) {
// ----重点:加载该命名空间下对应的xml
this.loadXmlResource();
// ---- 下面是注解方式
// 为configuration的this.loadedResources集合添加resource
this.configuration.addLoadedResource(resource);
// 设置命名空间,即类的全路径名
this.assistant.setCurrentNamespace(this.type.getName());
// 解析二级缓存注解CacheNamespace
this.parseCache();
// 解析二级缓存注解CacheNamespaceRef
this.parseCacheRef();
// 获取类的的所有方法
Method[] var2 = this.type.getMethods();
int var3 = var2.length;
// 遍历所有方法
for(int var4 = 0; var4 < var3; ++var4) {
Method method = var2[var4];
// 检查下method类型,不能是桥接方法和接口中的默认方法
if (this.canHaveStatement(method)) {
// select操作解析@select,@SelectProvider注释方法中的带有@ResultMap的方法
if (this.getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent() && method.getAnnotation(ResultMap.class) == null) {
this.parseResultMap(method);
}
try {
// 解析Statement
this.parseStatement(method);
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
}
// 解析IncompleteMethod,解析失败的方法。
this.parsePendingMethods();
}
查看加载该命名空间下对应的xml的this.loadXmlResource();
方法:
private void loadXmlResource() {
if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
// 将包名中的"."转换为"/"
String xmlResource = this.type.getName().replace('.', '/') + ".xml";
InputStream inputStream = this.type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
try {
inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
} catch (IOException var4) {
}
}
if (inputStream != null) {
// 以流的形式创建XMLMapperBuilder对象,
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
xmlParser.parse();
}
}
}
通过上面的方法可以看出,和方式二通过resource属性加载文件和方式三通过url属性加载文件是一样的,继续跟进 xmlParser.parse();
方法:
public void parse() {
// 该节点是否被解析过或加载过
if (!this.configuration.isResourceLoaded(this.resource)) {
//解析mapper节点
this.configurationElement(this.parser.evalNode("/mapper"));
// 加入到已经解析的列表,防止重复解析
this.configuration.addLoadedResource(this.resource);
// 将mapper注册给Configuration
this.bindMapperForNamespace();
}
// 下面分别用来处理失败的<resultMap>、<cache-ref>、SQL语句
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
查看如何解析mapper节点的this.configurationElement(this.parser.evalNode("/mapper"));
方法:
private void configurationElement(XNode context) {
try {
// 获取mapper标签的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.isEmpty()) {
// 绑定当前命名空间
this.builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref标签
this.cacheRefElement(context.evalNode("cache-ref"));
// 解析cache标签
this.cacheElement(context.evalNode("cache"));
// 解析parameterMap标签