mybatis-IO model
1. 概述
本文,我们来分享 MyBatis 的 IO 模块,对应 io
包。如下图所示:
在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中,简单介绍了这个模块如下:
资源加载模块,主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能 。
本文涉及的类如下图所示:
2. ClassLoaderWrapper
org.apache.ibatis.io.ClassLoaderWrapper
,ClassLoader 包装器。可使用多个 ClassLoader 加载对应的资源,直到有一成功后返回资源。
2.1 构造方法
// ClassLoaderWrapper.java
|
defaultClassLoader
属性,默认 ClassLoader 对象。目前不存在初始化该属性的构造方法。可通过ClassLoaderWrapper.defaultClassLoader = xxx
的方式,进行设置。systemClassLoader
属性,系统 ClassLoader 对象。在构造方法中,已经初始化。
2.2 getClassLoaders
#getClassLoaders(ClassLoader classLoader)
方法,获得 ClassLoader 数组。代码如下:
// ClassLoaderWrapper.java
|
2.3 getResourceAsURL
#getResourceAsURL(String resource, ...)
方法,获得指定资源的 URL 。代码如下:
// ClassLoaderWrapper.java
|
- 先调用
#getClassLoaders(ClassLoader classLoader)
方法,获得 ClassLoader 数组。 -
再调用
#getResourceAsURL(String resource, ClassLoader[] classLoader)
方法,获得指定资源的 InputStream 。代码如下:// ClassLoaderWrapper.java
/**
* Get a resource as a URL using the current class path
*
* @param resource - the resource to locate
* @param classLoader - the class loaders to examine
* @return the resource or null
*/
URL getResourceAsURL(String resource, ClassLoader[] classLoader) {
URL url;
// 遍历 ClassLoader 数组
for (ClassLoader cl : classLoader) {
if (null != cl) {
// 获得 URL ,不带 /
// look for the resource as passed in...
url = cl.getResource(resource);
// 获得 URL ,带 /
// ...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;
}
2.4 getResourceAsStream
#getResourceAsStream(String resource, ...)
方法,获得指定资源的 InputStream 对象。代码如下:
// ClassLoaderWrapper.java
|
- 先调用
#getClassLoaders(ClassLoader classLoader)
方法,获得 ClassLoader 数组。 -
再调用
#getResourceAsStream(String resource, ClassLoader[] classLoader)
方法,获得指定资源的 InputStream 。代码如下:// ClassLoaderWrapper.java
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
// 遍历 ClassLoader 数组
for (ClassLoader cl : classLoader) {
if (null != cl) {
// 获得 InputStream ,不带 /
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
// 获得 InputStream ,带 /
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
// 成功获得到,返回
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}- 可使用多个 ClassLoader 加载对应的资源,直到有一成功后返回资源。
2.5 classForName
#classForName(String name, ...)
方法,获得指定类名对应的类。代码如下:
// ClassLoaderWrapper.java
|
- 先调用
#getClassLoaders(ClassLoader classLoader)
方法,获得 ClassLoader 数组。 -
再调用
#classForName(String name, ClassLoader[] classLoader)
方法,获得指定类名对应的类。代码如下:// ClassLoaderWrapper.java
Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {
// 遍历 ClassLoader 数组
for (ClassLoader cl : classLoader) {
if (null != cl) {
try {
// 获得类
Class<?> c = Class.forName(name, true, cl);
// 成功获得到,返回
if (null != c) {
return c;
}
} catch (ClassNotFoundException e) {
// we'll ignore this until all classloaders fail to locate the class
}
}
}
// 获得不到,抛出 ClassNotFoundException 异常
throw new ClassNotFoundException("Cannot find class: " + name);
}- 可使用多个 ClassLoader 加载对应的类,直到有一成功后返回类。
3. Resources
org.apache.ibatis.io.Resources
,Resource 工具类。
3.1 构造方法
// Resources.java
|
3.2 getResource
基于 classLoaderWrapper
属性的封装。
3.2.1 getResourceURL
#getResourceURL(String resource)
静态方法,获得指定资源的 URL 。代码如下:
// Resources.java
|
3.2.2 getResourceAsStream
#getResourceAsStream(String resource)
静态方法,获得指定资源的 InputStream 。代码如下:
// Resources.java
|
3.2.3 getResourceAsReader
#getResourceAsReader(String resource)
静态方法,获得指定资源的 Reader 。代码如下:
// Resources.java
|
3.2.4 getResourceAsFile
#getResourceAsFile(String resource)
静态方法,获得指定资源的 File 。代码如下:
// Resources.java
|
- 基于
classLoaderWrapper
属性的封装。
3.2.5 getResourceAsProperties
#getResourceAsProperties(ClassLoader loader)
静态方法,获得指定资源的 Properties 。代码如下:
// Resources.java
|
3.3 getUrl
3.3.1 getUrlAsStream
#getUrlAsStream(String urlString)
静态方法,获得指定 URL 。代码如下:
// Resources.java
|
3.3.2 getUrlAsReader
#getUrlAsReader(String urlString)
静态方法,指定 URL 的 Reader 。代码如下:
// Resources.java
|
3.3.3 getUrlAsProperties
#getUrlAsReader(String urlString)
静态方法,指定 URL 的 Properties 。代码如下:
// Resources.java
|
3.4 classForName
#classForName(String className)
静态方法,获得指定类名对应的类。代码如下:
// Resources.java
|
4. ResolverUtil
org.apache.ibatis.io.ResolverUtil
,解析器工具类,用于获得指定目录符合条件的类们。
4.1 Test
Test ,匹配判断接口。代码如下:
// ResolverUtil.java 内部类
|
4.1.1 IsA
IsA ,实现 Test 接口,判断是否为指定类。代码如下:
// ResolverUtil.java 内部类
|
4.1.2 AnnotatedWith
AnnotatedWith ,判断是否有指定注解。代码如下:
// ResolverUtil.java 内部类
|
4.2 构造方法
// ResolverUtil.java
|
4.3 find
#find(Test test, String packageName)
方法,获得指定包下,符合条件的类。代码如下:
// ResolverUtil.java
|
-
<1>
处,调用#getPackagePath(String packageName)
方法,获得包的路径。代码如下:// ResolverUtil.java
protected String getPackagePath(String packageName) {
return packageName == null ? null : packageName.replace('.', '/');
}
<2>
处,获得路径下的所有文件。详细解析,见 「5. VFS」 。-
<3>
处,遍历 Java Class 文件,调用#addIfMatching(Test test, String fqn)
方法,如果匹配,则添加到结果集。代码如下:// ResolverUtil.java
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());
}
}
* 使用对应的 `test` 的进行匹配。
4.3.1 findImplementations
#findImplementations(Class<?> parent, String... packageNames)
方法,判断指定目录下们,符合指定类的类们。代码如下:
// ResolverUtil.java
|
4.3.2 findAnnotated
#findAnnotated(Class<? extends Annotation> annotation, String... packageNames)
方法,判断指定目录下们,符合指定注解的类们。代码如下:
// ResolverUtil.java
|
5. VFS
org.apache.ibatis.io.VFS
,虚拟文件系统( Virtual File System )抽象类,用来查找指定路径下的的文件们。
5.1 静态属性
// VFS.java
|
IMPLEMENTATIONS
静态属性,内置的 VFS 实现类的数组。目前 VFS 有 JBoss6VFS 和 DefaultVFS 两个实现类。USER_IMPLEMENTATIONS
静态属性,自定义的 VFS 实现类的数组。可通过#addImplClass(Class<? extends VFS> clazz)
方法,进行添加。
5.2 getInstance
#getInstance()
方法,获得 VFS 单例。代码如下:
// VFS.java
|
- 单例有多种实现方式,该类采用的是“懒汉式,线程安全”,感兴趣的胖友,可以看看 《单例模式的七种写法》 。
INSTANCE
属性,最后通过#createVFS()
静态方法来创建,虽然USER_IMPLEMENTATIONS
和IMPLEMENTATIONS
有多种 VFS 的实现类,但是最终选择的是,最后一个符合的创建的 VFS 对象。
5.3 反射相关方法
因为 VFS 自己有反射调用方法的需求,所以自己实现了三个方法。代码如下:
// VFS.java
|
5.4 isValid
#isValid()
抽象方法,判断是否为合法的 VFS 。代码如下:
// VFS.java
|
- 该方法由子类实现。
5.5 list
#list(String path)
方法,获得指定路径下的所有资源。代码如下:
// VFS.java
|
-
先调用
#getResources(String path)
静态方法,获得指定路径下的 URL 数组。代码如下:// VFS.java
protected static List<URL> getResources(String path) throws IOException {
return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
} -
后遍历 URL 数组,调用
#list(URL url, String forPath)
方法,递归的列出所有的资源们。代码如下:// VFS.java
/**
* Recursively list the full resource path of all the resources that are children of the
* resource identified by a URL.
*
* @param url The URL that identifies the resource to list.
* @param forPath The path to the resource that is identified by the URL. Generally, this is the
* value passed to {@link #getResources(String)} to get the resource URL.
* @return A list containing the names of the child resources.
* @throws IOException If I/O errors occur
*/
protected abstract List<String> list(URL url, String forPath) throws IOException;- 该方法由子类进行实现。
5.5 DefaultVFS
org.apache.ibatis.io.DefaultVFS
,继承 VFS 抽象类,默认的 VFS 实现类。
5.5.1 isValid
// DefaultVFS.java
|
- 都返回
true
,因为默认支持。
5.5.2 list
#list(URL url, String path)
方法,递归的列出所有的资源们。代码如下:
// DefaultVFS.java
|
- 代码有点长,重点读懂
<1>
和<2>
处的代码,基本就可以了。大体逻辑就是,不断递归文件夹,获得到所有文件。设计到对 Jar 的处理,感兴趣的胖友,可以自己理解下。😈 艿艿暂时没看的特别细。 -
#findJarForResource(URL url)
方法,如果url
指向的是 Jar Resource ,则返回该 Jar Resource ,否则返回null
。代码如下:// DefaultVFS.java
protected URL findJarForResource(URL url) throws MalformedURLException {
if (log.isDebugEnabled()) {
log.debug("Find JAR URL: " + url);
}
// If the file part of the URL is itself a URL, then that URL probably points to the JAR
// 这段代码看起来比较神奇,虽然看起来没有 break 的条件,但是是通过 MalformedURLException 异常进行
// 正如上面英文注释,如果 URL 的文件部分本身就是 URL ,那么该 URL 可能指向 JAR
try {
for (; ; ) {
url = new URL(url.getFile());
if (log.isDebugEnabled()) {
log.debug("Inner URL: " + url);
}
}
} catch (MalformedURLException e) {
// This will happen at some point and serves as a break in the loop
}
// Look for the .jar extension and chop off everything after that
// 判断是否意 .jar 结尾
StringBuilder jarUrl = new StringBuilder(url.toExternalForm());
int index = jarUrl.lastIndexOf(".jar");
if (index >= 0) {
jarUrl.setLength(index + 4);
if (log.isDebugEnabled()) {
log.debug("Extracted JAR URL: " + jarUrl);
}
} else {
if (log.isDebugEnabled()) {
log.debug("Not a JAR: " + jarUrl);
}
return null; // 如果不以 .jar 结尾,则直接返回 null
}
// Try to open and test it
try {
URL testUrl = new URL(jarUrl.toString());
// 判断是否为 Jar 文件
if (isJar(testUrl)) {
return testUrl;
} else {
// WebLogic fix: check if the URL's file exists in the filesystem.
if (log.isDebugEnabled()) {
log.debug("Not a JAR: " + jarUrl);
}
// 获得文件
jarUrl.replace(0, jarUrl.length(), testUrl.getFile()); // 替换
File file = new File(jarUrl.toString());
// File name might be URL-encoded
if (!file.exists()) { // 处理路径编码问题
try {
file = new File(URLEncoder.encode(jarUrl.toString(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unsupported encoding? UTF-8? That's unpossible.");
}
}
// 判断文件存在
if (file.exists()) {
if (log.isDebugEnabled()) {
log.debug("Trying real file: " + file.getAbsolutePath());
}
testUrl = file.toURI().toURL();
// 判断是否为 Jar 文件
if (isJar(testUrl)) {
return testUrl;
}
}
}
} catch (MalformedURLException e) {
log.warn("Invalid JAR URL: " + jarUrl);
}
if (log.isDebugEnabled()) {
log.debug("Not a JAR: " + jarUrl);
}
return null;
}- 会判断要求,
url
以.jar
结尾。
- 会判断要求,
-
#isJar(URL url)
方法,判断是否为 JAR URL 。代码如下:// DefaultVFS.java
/** The magic header that indicates a JAR (ZIP) file. */
private static final byte[] JAR_MAGIC = {'P', 'K', 3, 4};
protected boolean isJar(URL url) {
return isJar(url, new byte[JAR_MAGIC.length]);
}
protected boolean isJar(URL url, byte[] buffer) {
InputStream is = null;
try {
is = url.openStream();
// 读取文件头
is.read(buffer, 0, JAR_MAGIC.length);
// 判断文件头的 magic number 是否符合 JAR
if (Arrays.equals(buffer, JAR_MAGIC)) {
if (log.isDebugEnabled()) {
log.debug("Found JAR: " + url);
}
return true;
}
} catch (Exception e) {
// Failure to read the stream means this is not a JAR
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
// Ignore
}
}
}
return false;
}
-
#listResources(JarInputStream jar, String path)
方法,遍历 Jar Resource 。代码如下:// DefaultVFS.java
protected List<String> listResources(JarInputStream jar, String path) throws IOException {
// Include the leading and trailing slash when matching names
// 保证头尾都是 /
if (!path.startsWith("/")) {
path = "/" + path;
}
if (!path.endsWith("/")) {
path = path + "/";
}
// Iterate over the entries and collect those that begin with the requested path
// 遍历条目并收集以请求路径开头的条目
List<String> resources = new ArrayList<>();
for (JarEntry entry; (entry = jar.getNextJarEntry()) != null; ) {
if (!entry.isDirectory()) {
// Add leading slash if it's missing
String name = entry.getName();
if (!name.startsWith("/")) {
name = "/" + name;
}
// Check file name
if (name.startsWith(path)) {
if (log.isDebugEnabled()) {
log.debug("Found resource: " + name);
}
// Trim leading slash
resources.add(name.substring(1));
}
}
}
return resources;
}
5.6 JBoss6VFS
org.apache.ibatis.io.JBoss6VFS
,继承 VFS 抽象类,基于 JBoss 的 VFS 实现类。使用时,需要引入如下:
<dependency>
|
因为实际基本没使用到,所以暂时不分析这个类