1、概述
Mybatis的IO模块对应的是io包,如下图所示:
IO模块所涉及的类如下图所示:
2. ClassLoaderWrapper
org.apache.ibatis.io.ClassLoaderWrapper
,ClassLoader 包装器。可使用多个 ClassLoader 加载对应的资源,直到有一成功后返回资源。
2.1 构造方法
/**
* 默认 ClassLoader 对象
*/
ClassLoader defaultClassLoader;
/**
* 系统 ClassLoader 对象
*/
ClassLoader systemClassLoader;
ClassLoaderWrapper() {
try {
systemClassLoader = ClassLoader.getSystemClassLoader();
} catch (SecurityException ignored) {
// AccessControlException on Google App Engine
}
}
defaultClassLoader 属性:默认 ClassLoader 对象。目前不存在初始化该属性的构造方法。可ClassLoaderWrapper.defaultClassLoader = xxx 的方式,进行设置。
systemClassLoader 属性:系统 ClassLoader 对象。在构造方法中,已经初始化。
2.2 getClassLoaders
getClassLoaders(ClassLoader classLoader)
方法,获得 ClassLoader 数组。代码如下:
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
2.3 getResourceAsURL
getResourceAsURL(String resource, ...)
方法,获得指定资源的 URL 。代码如下:
//使用当前类路径获取资源作为URL
public URL getResourceAsURL(String resource) {
return getResourceAsURL(resource, getClassLoaders(null));
}
//从类路径获取资源,从特定的类加载器开始
public URL getResourceAsURL(String resource, ClassLoader classLoader) {
return getResourceAsURL(resource, getClassLoaders(classLoader));
}
先调用 #getClassLoaders(ClassLoader classLoader) 方法,获得 ClassLoader 数组。
再调用 #getResourceAsURL(String resource, ClassLoader[] classLoader) 方法,获得指定资源的 URL 。代码如下:
//使用当前类路径获取资源作为URL
URL getResourceAsURL(String resource, ClassLoader[] classLoader) {
URL url;
// 遍历 ClassLoader 数组
for (ClassLoader cl : classLoader) {
if (null != cl) {
// 获得 URL ,不带 "/"
url = cl.getResource(resource);
// 获得 URL ,带 "/"
if (null == url) {
url = cl.getResource("/" + resource);
}
// 成功获得到,返回
if (null != url) {
return url;
}
}
}
return null;
}
2.4 getResourceAsStream
getResourceAsStream(String resource, ...)
方法,获得指定资源的 InputStream 对象。与上面的方法逻辑差不多,只是返回结果不一样,一个返回URL,另一个返回InputStream,所以就不看源码了。
3. Resources
3.1getResource
基于 classLoaderWrapper
属性的封装。有如下方式:getResourceURL、getResourceAsStream、getResourceAsProperties、getResourceAsReader、getResourceAsFile等,逻辑差不多我们就以第一个为例。
3.1.1 getResourceURL
getResourceURL(String resource)
静态方法,获得指定资源的 URL 。代码如下:
//返回类路径上资源的URL
public static URL getResourceURL(String resource) throws IOException {
return getResourceURL(null, resource);
}
//返回类路径上资源的URL
public static URL getResourceURL(ClassLoader loader, String resource) throws IOException {
URL url = classLoaderWrapper.getResourceAsURL(resource, loader);
if (url == null) {
throw new IOException("Could not find resource " + resource);
}
return url;
}
3.2 getUrl
3.2.1 getUrlAsStream
getUrlAsStream(String urlString)
静态方法,获得 URL的作为输入流 。代码如下:
//获取URL作为输入流
public static InputStream getUrlAsStream(String urlString) throws IOException {
URL url = new URL(urlString);
// 打开 URLConnection
URLConnection conn = url.openConnection();
return conn.getInputStream();
}
3.2.2 getUrlAsReader
getUrlAsReader(String urlString)
静态方法,获得 URL 的 Reader 。代码如下:
//获取URL作为Reader
public static Reader getUrlAsReader(String urlString) throws IOException {
Reader reader;
if (charset == null) {
reader = new InputStreamReader(getUrlAsStream(urlString));
} else {
reader = new InputStreamReader(getUrlAsStream(urlString), charset);
}
return reader;
}
3.2.3 getUrlAsProperties
getUrlAsProperties(String urlString)静态方法,获得URL 的 Properties对象 。代码如下:
//获取URL作为Properties对象
public static Properties getUrlAsProperties(String urlString) throws IOException {
Properties props = new Properties();
try (InputStream in = getUrlAsStream(urlString)) {
props.load(in);
}
return props;
}
3.3 classForName
classForName(String className)
静态方法,获得指定类名对应的类。代码如下:
//加载一个指定的类
public static Class> classForName(String className) throws ClassNotFoundException {
return classLoaderWrapper.classForName(className);
}
4. ResolverUtil
org.apache.ibatis.io.ResolverUtil
,解析器工具类,用于获得指定目录符合条件的类。
4.1 Test
//一个简单的接口,指定如何测试类以确定它们是否包含在ResolverUtil生成的结果中。
public interface Test {
//包含返回True,否则返回false。
boolean matches(Class> type);
}
4.1.1 IsA
IsA ,实现 Test 接口,判断是否为指定类。代码如下:
public static class IsA implements Test {
private Class> parent;
public IsA(Class> parentType) {
this.parent = parentType;
}
@Override
public boolean matches(Class> type) {
return type != null && parent.isAssignableFrom(type);
}
@Override
public String toString() {
return "is assignable to " + parent.getSimpleName();
}
}
4.1.2 AnnotatedWith
AnnotatedWith ,判断是否有指定注解。代码如下:
public static class AnnotatedWith implements Test {
private Class extends Annotation> annotation;
public AnnotatedWith(Class extends Annotation> annotation) {
this.annotation = annotation;
}
@Override
public boolean matches(Class> type) {
return type != null && type.isAnnotationPresent(annotation);
}
}
4.2 find
find(Test test, String packageName)
方法,获得指定包下,符合条件的类。代码如下:
public ResolverUtil find(Test test, String packageName) {
// <1> 获得包的路径
String path = getPackagePath(packageName);
try {
// <2> 获得路径下的所有文件
List children = VFS.getInstance().list(path);
// <3> 遍历
for (String child : children) {
// 是 Java Class
if (child.endsWith(".class")) {
// 如果匹配,则添加到结果集
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
<1>
处,调用 getPackagePath(String packageName)
方法,获得包的路径。代码如下:
//将Java包名称转换为可以通过调用查找的路径
protected String getPackagePath(String packageName) {
return packageName == null ? null : packageName.replace('.', '/');
}
<2> 处,获得路径下的所有文件。详细解析,见 下面的 VFS 。
<3>
处,遍历 Java Class 文件,调用 addIfMatching(Test test, String fqn)
方法,如果匹配,则添加到结果集。代码如下:
protected void addIfMatching(Test test, String fqn) {
try {
// 1.获得全类名
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 + "]");
}
// 2.加载类
Class> type = loader.loadClass(externalName);
//3. 判断是否匹配
if (test.matches(type)) {
matches.add((Class) type);
}
} catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a " +
t.getClass().getName() + " with message: " + t.getMessage());
}
}
4.2.1 findImplementations
findImplementations(Class> parent, String... packageNames)
方法,判断指定目录下们,符合指定类的类们。代码如下:
public ResolverUtil findImplementations(Class> parent, String... packageNames) {
if (packageNames == null) {
return this;
}
Test test = new IsA(parent);
for (String pkg : packageNames) {
find(test, pkg);
}
return this;
}
4.2.2 findAnnotated
findAnnotated(Class extends Annotation> annotation, String... packageNames)
方法,判断指定目录下们,符合指定注解的类们。代码如下:
public ResolverUtil findAnnotated(Class extends Annotation> annotation, String... packageNames) {
if (packageNames == null) {
return this;
}
Test test = new AnnotatedWith(annotation);
for (String pkg : packageNames) {
find(test, pkg);
}
return this;
}
5. VFS
org.apache.ibatis.io.VFS
,虚拟文件系统( Virtual File System )抽象类,用来查找指定路径下的的文件们。
5.1 静态属性
// 内置的 VFS 实现类的数组
public static final Class>[] IMPLEMENTATIONS = {JBoss6VFS.class, DefaultVFS.class};
// 自定义的 VFS 实现类的数组
public static final List> USER_IMPLEMENTATIONS = new ArrayList<>();
public static void addImplClass(Class extends VFS> clazz) {
if (clazz != null) {
USER_IMPLEMENTATIONS.add(clazz);
}
}
IMPLEMENTATIONS 静态属性:内置的 VFS 实现类的数组。目前 VFS 有 JBoss6VFS 和 DefaultVFS 两个实现类。
USER_IMPLEMENTATIONS 静态属性:自定义的 VFS 实现类的数组。可通过 #addImplClass(Class extends VFS> clazz) 方法,进行添加。
5.2 getInstance
getInstance()
方法,获得 VFS 单例。代码如下:
public static VFS getInstance() {
return VFSHolder.INSTANCE;
}
//VFS单例实例
private static class VFSHolder {
static final VFS INSTANCE = createVFS();
@SuppressWarnings("unchecked")
static VFS createVFS() {
//首先尝试默认的,然后自己实现的
List> impls = new ArrayList<>();
impls.addAll(USER_IMPLEMENTATIONS);
impls.addAll(Arrays.asList((Class extends VFS>[]) IMPLEMENTATIONS));
// 创建 VFS 对象,选择最后一个符合的
VFS vfs = null;
for (int i = 0; vfs == null || !vfs.isValid(); i++) {
Class extends VFS> impl = impls.get(i);
try {
vfs = impl.newInstance();
if (vfs == null || !vfs.isValid()) {
if (log.isDebugEnabled()) {
log.debug("VFS implementation " + impl.getName() +
" is not valid in this environment.");
}
}
} catch (InstantiationException | IllegalAccessException e) {
log.error("Failed to instantiate " + impl, e);
return null;
}
}
if (log.isDebugEnabled()) {
log.debug("Using VFS adapter " + vfs.getClass().getName());
}
return vfs;
}
}
5.3 isValid
isValid()
抽象方法,判断是否为合法的 VFS 。代码如下:
//由子类实现
public abstract boolean isValid();
5.4 list
list(String path)
方法,获得指定路径下的所有资源。代码如下:
public List list(String path) throws IOException {
List names = new ArrayList<>();
for (URL url : getResources(path)) {
names.addAll(list(url, path));
}
return names;
}
先调用 #getResources(String path)
静态方法,获得指定路径下的 URL 数组。代码如下:
protected static List getResources(String path) throws IOException {
return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
}
后遍历 URL 数组,调用 #list(URL url, String forPath)
方法,递归的列出所有的资源们。代码如下:
//由子类实现
protected abstract List list(URL url, String forPath) throws IOException;
5.5 DefaultVFS
org.apache.ibatis.io.DefaultVFS
,继承 VFS 抽象类,默认的 VFS 实现类。
5.5.1 isValid
//都返回 true ,因为默认支持
@Override
public boolean isValid() {
return true;
}
5.5.2 list
list(URL url, String path)
方法,递归的列出所有的资源们。代码如下:
@Override
public List list(URL url, String path) throws IOException {
InputStream is = null;
try {
List resources = new ArrayList<>();
// 如果 url 指向的是 Jar Resource ,则返回该 Jar Resource ,否则返回 null
URL jarUrl = findJarForResource(url);
if (jarUrl != null) {
is = jarUrl.openStream();
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
// 遍历 Jar Resource
resources = listResources(new JarInputStream(is), path);
} else {
List children = new ArrayList<>();
try {
// 判断 URL是否为 JAR
if (isJar(url)) {
// 即使URL引用的资源,实际上不是JAR,某些版本的JBoss VFS也可能会提供JAR流
is = url.openStream();
try (JarInputStream jarInput = new JarInputStream(is)) {
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null; ) {
if (log.isDebugEnabled()) {
log.debug("Jar entry: " + entry.getName());
}
children.add(entry.getName());
}
}
} else {
// <1> 获得路径下的所有资源
is = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
List lines = new ArrayList<>();
for (String line; (line = reader.readLine()) != null; ) {
if (log.isDebugEnabled()) {
log.debug("Reader entry: " + line);
}
lines.add(line);
if (getResources(path + "/" + line).isEmpty()) {
lines.clear();
break;
}
}
if (!lines.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
children.addAll(lines);
}
}
} catch (FileNotFoundException e) {
if ("file".equals(url.getProtocol())) {
File file = new File(url.getFile());
if (log.isDebugEnabled()) {
log.debug("Listing directory " + file.getAbsolutePath());
}
if (file.isDirectory()) {
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
children = Arrays.asList(file.list());
}
} else {
// No idea where the exception came from so rethrow it
throw e;
}
}
<2> 计算 prefix
String prefix = url.toExternalForm();
if (!prefix.endsWith("/")) {
prefix = prefix + "/";
}
// Iterate over immediate children, adding files and recursing into directories
<3> 遍历子路径
for (String child : children) {
// 添加到 resources 中
String resourcePath = path + "/" + child;
resources.add(resourcePath);
// 递归遍历子路径,并将结果添加到 resources 中
URL childUrl = new URL(prefix + child);
resources.addAll(list(childUrl, resourcePath));
}
}
return resources;
} finally {
// 关闭文件流
if (is != null) {
try {
is.close();
} catch (Exception e) {
// Ignore
}
}
}
}