MyBatis基础支持层位于 Mybatis 整体架构的最底层,支撑着 Mybatis 的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块,不仅仅为 Mybatis 提供基础支撑,也可以在合适的场景中直接复用。

整体架构

这篇文章介绍MyBatis的资源加载模块

类加载器

Java虚 拟 机中的类 加载 器(ClassLoader)负 责 加载 来 自文件系统 、网 络 或其他来 源的类 文 件。Java虚 拟 机中的类 加载 器默认 使用的是双 亲 委派模式,如图所示,其中有三种 默认 使 用 的 类 加 载 器 ,分 别 是 Bootstrap ClassLoaderExtension ClassLoaderSystem ClassLoader (也 被称 为 ApplicationClassLoader),每种 类 加载 器都己经 确 定从 哪 个 位置加载 类 文件。

BootstrapClassLoader负 责 加载 JDK自带 的rt.jar包中的类 文件,它 是所有类 加载 器的父加载 器,Bootstrap ClassLoader没 有任何父类 加载 器。Extension ClassLoader负 责 加 载 Java的扩 展类 库 ,也就是从 jre/lib/ext目录 下或者java.ext.dirs系统 属 性指定的目录 下加载 类 。

SystemClassLoader负 责 从 classpath环 境变 量中加载 类 文件,classpath环 境变 量通常由-classpath-cp命令行选 项 来 定义 ,或 是 由 JAR中 Manifest文 件 的 classpath属 性指定。System ClassLoaderExtensionClassLoader的子加载 器。

image-20200608173325045

根据双亲委派模式,在加载类文件时,子加载器首先会将加载请求委托给它的父加载器。

父加载器会检测自己是否已经加载过该类,如果己加载则加载过程结束;如果未加载则请求继 续 向上传 递 ,直到BootstrapClassLoader。

如果在请 求向上委托的过 程中,始终 未检 测 到该 类 己 加载 ,则 从 BootstrapClassLoader开 始尝 试 从 其对 应 路径 中加载 该 类 文件,如果加载 失败 则 由 子加载器继续尝试加载,直至发起加载请求的子加载器位为止。

双 亲 委派模式可以保证 两 点:

一是子加载 器可以使用父加载 器己加载 的类 ,而父加载 器无 法使用子加载 器已加载 的类 ;

二是父加载 器已加载 过 的类 无法被子加载 器再次加载 。

这 样 就可以保证 JVM的安全性和稳 定性。

ClassLoaderWrapper

上上一小节中了解了类 加载 器的常见 使用方式。在 MyBatis的 IO 包中封装 了 ClassLoader以及读 取资 源文件的相关 API。

IO包 中 提 供 的 ClassLoaderWrapper是 一 个 ClassLoader的包装 器,其中包含了多个ClassLoader对 象。

通过 调 整多个 类 加载 器的使用顺 序,ClassLoaderWrapper可以确 保返回给 系 统 使用的是正确 的类 加载 器。

使 用 ClassLoaderWrapper就如同使用一个 ClassLoader对 象, ClassLoaderWrapper会 按照指定的顺 序依次检 测 其中封装 的ClassLoader对 象,并 从 中选 取第一 个 可用的ClassLoader完成相关 功能。

ClassLoaderWrapper的 主 要 功 能 可 以 分 为 三 类 , 分 别 是 getResourceAsURL()方 法classForName()方法getResourceAsStream()方法,这 三个 方法都有多个 重载 ,这 三类 方法最终 都会 调 用参 数 为 StringClassLoader[]的重载 。

getResourceAsURL()代码如下,其他的类似:

public URL getResourceAsURL(String resource, ClassLoader classLoader) {
return getResourceAsURL(resource, getClassLoaders(classLoader));
}

// 返回ClassLoader[], 该方法指明类加载器的使用顺序
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}

URL getResourceAsURL(String resource, ClassLoader[] classLoader) {

URL url;

for (ClassLoader cl : classLoader) {

if (null != cl) {

// look for the resource as passed in...
url = cl.getResource(resource);

// ...but some class loaders want this leading "/", so we'll add it
// and try again if we didn't find the resource
if (null == url) {
url = cl.getResource("/" + resource);
}

// "It's always in the last place I look for it!"
// ... because only an idiot would keep looking for it after finding it, so stop looking already.
if (null != url) {
return url;
}

}

}

// didn't find it anywhere.
return null;

}

Resources是一个 提供了多个 静 态 方法的工具类 ,其中封装 了一个 ClassLoaderWrapper类 型 的静 态 字段,Resources提供的这 些静 态 工具都是通过 调 用该 ClassLoaderWrapper对 象的相应 方 法实 现 的。

ResolverUtil

ResolverUtil可以根据指定的条件查找指定包下的类 ,其中使用的条件由Test接口表示。

ResolverUtil中使用classLoader字 段 (ClassLoader类 型)记 录 了当 前使用的类 加载 器,默认 情 况 下,使用的是当 前线 程上下文绑 定的ClassLoader,我们 可以通过 setClassLoader()方法修改使 用类加载器。

MyBatis提供了两 个 常用的Test接口实 现 ,分别 是IsAAnnotatedWith,如图 所示。 IsA用于检 测 类 是否继 承了指定的类 或接口,AnnotatedWith用于检 测 类 是否添加了指定的注解。 开 发 人员 也可以自己实 现 Test接口,实 现 指定条 件的检 测 。

image-20200608175854576

Test接口中定义 了 matches()方法,它 用于检 测 指定类 是否符合条 件:

public interface Test {
/**
* Will be called repeatedly with candidate classes. Must return True if a class
* is to be included in the results, false otherwise.
*/
boolean matches(Class<?> type);
}

IsAAnnotatedWith的具体 实 现 如下

public static class IsA implements Test {
private Class<?> parent;

/** Constructs an IsA test using the supplied Class as the parent class/interface. */
public IsA(Class<?> parentType) {
this.parent = parentType;
}

/** Returns true if type is assignable to the parent type supplied in the constructor. */
@Override
public boolean matches(Class<?> type) {
return type != null && parent.isAssignableFrom(type);
}

@Override
public String toString() {
return "is assignable to " + parent.getSimpleName();
}
}

public static class AnnotatedWith implements Test {
private Class<? extends Annotation> annotation;

/** Constructs an AnnotatedWith test for the specified annotation type. */
public AnnotatedWith(Class<? extends Annotation> annotation) {
this.annotation = annotation;
}

/** Returns true if the type is annotated with the class provided to the constructor. */
@Override
public boolean matches(Class<?> type) {
return type != null && type.isAnnotationPresent(annotation);
}

@Override
public String toString() {
return "annotated with @" + annotation.getSimpleName();
}
}

默认 情况 下,使用Thread.currentThread().getContextClassLoader()这 个 类 加载 器加载 符合条 件的类 ,我们 可以在调 用find()方法之前,调 用 setClassLoader(ClassLoader)设 置需要使用的 ClassLoader,这 个 ClassLoader可 以 从 ClassLoaderWrapper中 获 取 合 适 的 类 加 载 器 。

ResolverUtil的使用案例:

ResolverUtil<ActionBean> resolver = new ResolverUtil<ActionBean>();
// 在pkg1和pkg2这个包下查找实现了ActionBean这个接口的类
resolver.findImplementation(ActionBean.class, pkg1, pkg2);
// 在pkg1包下查找符合CustomerTest条件的类
resolver.find(new CustomTest(), pkg1);
// 在pkg2包下查找符合CustomerTest条件的类
resolver.find(new CustomTest(), pkg2);
// 获取上面三次查找的结果
Collection<ActionBean> beans = resolver.getClasses();

ResolverUtil.findImplementations()方法和ResolverUtil.findAnnotated()方法都是依赖RescolverUtil.find()方法实现的,findImplementations()方法会创建IsA对象作为检测条件,findAnnotated()方法会创建AnnotatedWith对象作为检测条件。

image-20200608214416619

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;
}

protected void addIfMatching(Test test, String fqn) {
try {
// fqn是类所在完全限定名,即包括其所在包的包名
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("...");
}

Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("...");
}
}

VFS

VFS表示虚 拟 文件系统 (VirtualFileSystem),它 用来 査找 指定路径 下的资 源。VFS是一个 抽象类 ,MyBatis中提供了 JBoss6VFS和 DefaultVFS两 个 VFS的实 现 ,如图所示。用户 也可以提供自定义 的VFS实 现 类 。

VFS采用单例模式实现

image-20200608220219508

参考

  • 《MyBatis技术内幕》

  • 部分图片来源——《MyBatis技术内幕》