Mybatis--IO模块

1、概述

 Mybatis的IO模块对应的是io包,如下图所示: 

                                     Mybatis--IO模块_第1张图片

IO模块所涉及的类如下图所示: 

                           Mybatis--IO模块_第2张图片

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

    public AnnotatedWith(Class 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 annotation, String... packageNames) 方法,判断指定目录下们,符合指定注解的类们。代码如下:

public ResolverUtil findAnnotated(Class 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 clazz) {
    if (clazz != null) {
        USER_IMPLEMENTATIONS.add(clazz);
    }
}

IMPLEMENTATIONS 静态属性:内置的 VFS 实现类的数组。目前 VFS 有 JBoss6VFS 和 DefaultVFS 两个实现类。
            USER_IMPLEMENTATIONS 静态属性:自定义的 VFS 实现类的数组。可通过 #addImplClass(Class 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[]) IMPLEMENTATIONS));

        // 创建 VFS 对象,选择最后一个符合的
        VFS vfs = null;
        for (int i = 0; vfs == null || !vfs.isValid(); i++) {
            Class 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
            }
        }
    }
}

 

你可能感兴趣的:(mybatis)