Mybatis源码分析(二)Mybatis-config.xml的初始化

在这里插入图片描述
📷📷📷(重庆)涂鸦一条街

系列文章:

文章状态时间描述
(一)Mybatis 基本使用已复习2022-12-14对Mybtais的基本使用,能够开发
(二)Mybatis-config.xml的初始化已复习2023-02-10对我们编写的mybatis配置文件的解析
(三)SqlSessionFactory的初始化已复习2023-02-11SqlSession会话工厂的初始化
(四)Mapper文件的解析已复习2023-02-12主要对我们编写的Mapper.xml进行解析
(五)SqlSession的创建已复习2023-02-13主要介绍构建DefaultSqlSessionFactory
(六)Mapper的接口代理已复习2023-02-14如何通过动态代理来执行我们编写的方法
(七)MapperMethod的INSERT分析已复习2023-02-15通过代理对象来执行Insert语句,返回结果
(八)MapperMethod的Select分析已复习2023-02-16通过代理对象来执行Select语句,返回结果
(九)Mybatis的PreparedStatement已复习2023-02-17预处理语句的常见,以及与数据库打交道
(十)Mybatis的结果隐射已复习2023-02-18数据库结果与实体类对象的转换
(十一)Mybatis的一级缓存与二级缓存已复习2023-02-24Mybatis中一级缓存与二级缓存
(十二)Mybatis的插件开发及原理分析已复习2023-02-25Mybatis中的插件运行机制与开发
(十三)Mybatis的四大组件已复习2023-03-04Mybatis中的四大组件的梳理
(十四)Mybatis的设计模式梳理已复习2023-03-11Mybatis中设计模式的整理
(十五)Spring-Mybatis整理已复习2023-03-18Spring与Mybatis整合
Mybatis源码分析补充(一)JDBC详解已复习2023-03-17JDBC详解
  • 官网:mybatis – MyBatis 3 | 简介
    上一篇文章我们介绍了Mybatis与SpringBoot的整合,我们可以掌握Mybatis的基本用法,到这我们需要来了解一条Sql的执行的基本处理过程,这篇文章主要介绍Mybatsi的文件的加载准备工作?

学习的知识?

  1. Java 类的加载机制,以及ClassLoader的加载过程
  2. VFS(Virtual File System)工具类,屏蔽底层磁盘系统差异
  3. ResolverUtil工具类,完成对类的筛选
  4. 配置文件的初始化,文件的寻找以及转换成输入流的过程

一 环境搭建

  • 依赖
      <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>

  • 编写Mapper
package com.shu;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.awt.print.Pageable;
import java.util.List;

/**
 * @description:
 * @author: shu
 * @createDate: 2022/12/13 19:43
 * @version: 1.0
 */
@Mapper
@Repository
public interface UserMapper {
    /**
     * 通过ID查询单条数据
     *
     * @param id 主键
     * @return 实例对象
     */
    User queryById(Integer id);
    /**
     * 分页查询指定行数据
     *
     * @param user 查询条件
     * @param pageable 分页对象
     * @return 对象列表
     */
    List<User> queryAllByLimit(User user);
    /**
     * 统计总行数
     *
     * @param user 查询条件
     * @return 总行数
     */
    long count(User user);
    /**
     * 新增数据
     *
     * @param user 实例对象
     * @return 影响行数
     */
    int insert(User user);
    /**
     * 批量新增数据
     *
     * @param entities List<User> 实例对象列表
     * @return 影响行数
     */
    int insertBatch(@Param("entities") List<User> entities);
    /**
     * 批量新增或按主键更新数据
     *
     * @param entities List<User> 实例对象列表
     * @return 影响行数
     */
    int insertOrUpdateBatch(@Param("entities") List<User> entities);
    /**
     * 更新数据
     *
     * @param user 实例对象
     * @return 影响行数
     */
    int update(User user);
    /**
     * 通过主键删除数据
     *
     * @param id 主键
     * @return 影响行数
     */
    int deleteById(Integer id);
}

  • 编写mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shu.UserMapper">
    <resultMap type="com.shu.User" id="UserMap">
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="email" column="email" jdbcType="VARCHAR"/>
        <result property="age" column="age" jdbcType="INTEGER"/>
        <result property="sex" column="sex" jdbcType="INTEGER"/>
        <result property="schoolname" column="schoolName" jdbcType="VARCHAR"/>
    </resultMap>

    <!-- 通过ID查询单条数据 -->
    <select id="queryById" resultMap="UserMap">
        select
            id,name,email,age,sex,schoolName
        from user
        where id = #{id}
    </select>

    <!--分页查询指定行数据-->
    <select id="queryAllByLimit" resultMap="UserMap">
        select
        id,name,email,age,sex,schoolName
        from user
        <where>
            <if test="id != null and id != ''">
                and id = #{id}
            </if>
            <if test="name != null and name != ''">
                and name = #{name}
            </if>
            <if test="email != null and email != ''">
                and email = #{email}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="schoolname != null and schoolname != ''">
                and schoolName = #{schoolname}
            </if>
        </where>
    </select>

    <!--统计总行数-->
    <select id="count" resultType="java.lang.Long">
        select count(1)
        from user
        <where>
            <if test="id != null and id != ''">
                and id = #{id}
            </if>
            <if test="name != null and name != ''">
                and name = #{name}
            </if>
            <if test="email != null and email != ''">
                and email = #{email}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="schoolname != null and schoolname != ''">
                and schoolName = #{schoolname}
            </if>
        </where>
    </select>

    <!--新增数据-->
    <insert id="insert" keyProperty="id" useGeneratedKeys="true">
        insert into user(id,name,email,age,sex,schoolName)
        values (#{id},#{name},#{email},#{age},#{sex},#{schoolname})
    </insert>

    <!-- 批量新增数据 -->
    <insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">
        insert into user(id,name,email,age,sex,schoolName)
        values
        <foreach collection="entities" item="entity" separator=",">
            (#{entity.id},#{entity.name},#{entity.email},#{entity.age},#{entity.sex},#{entity.schoolname})
        </foreach>
    </insert>

    <!-- 批量新增或按主键更新数据 -->
    <insert id="insertOrUpdateBatch" keyProperty="id" useGeneratedKeys="true">
        insert into user(id,name,email,age,sex,schoolName)
        values
        <foreach collection="entities" item="entity" separator=",">
            (#{entity.id},#{entity.name},#{entity.email},#{entity.age},#{entity.sex},#{entity.schoolname})
        </foreach>
        on duplicate key update
        id=values(id),
        name=values(name),
        email=values(email),
        age=values(age),
        sex=values(sex),
        schoolName=values(schoolName)
    </insert>

    <!-- 更新数据 -->
    <update id="update">
        update user
        <set>
            <if test="id != null and id != ''">
                id = #{id},
            </if>
            <if test="name != null and name != ''">
                name = #{name},
            </if>
            <if test="email != null and email != ''">
                email = #{email},
            </if>
            <if test="age != null and age != ''">
                age = #{age},
            </if>
            <if test="sex != null and sex != ''">
                sex = #{sex},
            </if>
            <if test="schoolname != null and schoolname != ''">
                schoolName = #{schoolname},
            </if>
        </set>
        where id = #{id}
    </update>

    <!--通过主键删除-->
    <delete id="deleteById">
        delete from user where id = #{id}
    </delete>
</mapper>
  • 编写Mybatis-conf.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <!-- 别名-->
  <!-- 环境   -->
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="mapper/UserMapper.xml"/>
  </mappers>

</configuration>
  • 编写测试用例
package com.shu;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

@SpringBootTest
class MybatisDemo02ApplicationTests {

  @Test
  void contextLoads() {
    // 第一阶段:MyBatis的初始化阶段
    String resource = "mybatis-config.xml";
    // 得到配置文件的输入流
    InputStream inputStream = null;
    try {
      inputStream = Resources.getResourceAsStream(resource);
    } catch (IOException e) {
      e.printStackTrace();
    }
    // 得到SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    // 第二阶段:数据读写阶段
    try (SqlSession session = sqlSessionFactory.openSession()) {
      // 找到接口对应的实现
      UserMapper userMapper = session.getMapper(UserMapper.class);
      // 组建查询参数
      User userParam = new User();
      userParam.setSchoolname("Sunny School");
      // 调用接口展开数据库操作
      List<User> userList =  userMapper.queryAllByLimit(userParam);
      // 打印查询结果
      for (User user : userList) {
        System.out.println("name : " + user.getName() + " ;  email : " + user.getEmail());
      }
    }
  }

}

到这我们的代码编写完毕,下一步我们来分析其执行过程

二 配置文件初始化

过程:流程图
Mybatis-config.png
上面我们写了mybatis-config.xml文件,在代码开头我们可以看见进行配置文件的初始化
下面我们来分析一下他说如何拿到mybatis-config.xml这个文件的,在分析之前我们需要里了解一下ClassLoader。

  	 // 第一阶段:MyBatis的初始化阶段
        String resource = "mybatis-config.xml";
        // 得到配置文件的输入流
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }

2.1 ClassLoader

  • 我们从字面上理解就是类加载器,下面但是Jvm的类加载过程,类的加载就是 Java虚拟机将描述类的数据从 Class文件加载到 JVM的过程,在这一过程中会对 Class文件进行数据加载、连接和初始化,最终形成可以被虚拟机直接使用的 Java类。

  • 当然JVM 在一开始就可能把所有的类都加载,那么可能撑死,按需加载才是王道

2.1.1 Java 类加载器


1,引导类加载器 (BootstrapClassLoader
负责加载系统类(通常从JAR的rt.jar中进行加载),它是虚拟机不可分割的一部分,通常使用C语言实现,引导类加载器没有对应的ClassLoader对象
2,扩展类加载器 (ExtClassLoader
扩展类加载器用于从jre/lib/txt目标加载“标准的扩展”。可以将jar文件放入该目录,这样即使没有任何类路径,扩展类加载器也可以找到其中的各个类
3,系统类加载器 (AppClassLoader
系统类加载器用于加载应用类,它在由ClASSPATH环境变量或者-classpath命令行选项设置的类路径的目录或者是jar/ZIP文件里查找这些类

我们可以做个小测试,测试代码如下:

    /**
     * 类加载机制
     */
    @Test
    public void ResourcesTest(){
        // 应用程序类加载器(Application ClassLoader):用于加载用户类路径(Classpath)上的类,是Java应用程序的类加载器。
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("应用程序类加载器:"+systemClassLoader.toString());
        // 扩展类加载器(Extension ClassLoader):用于加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的类。
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println("扩展类加载器:"+parent.toString());
        // 启动类加载器(Bootstrap ClassLoader):用于加载Java运行时环境所需要的类,它加载的类是由C++编写的,并由虚拟机自身启动。
//        ClassLoader parentParent = parent.getParent();
//        System.out.println("启动类加载器:"+parentParent.toString());
    }

注意:Bootstrap ClassLoader会报错,因为Bootstrap ClassLoader是虚拟机的一部分,由C++进行编写

加载顺序

  1. BootstrapClassLoader
  2. ExtClassLoader
  3. AppClassLoader
  • 关于classLoader的详细信息请参考文章:一看你就懂,超详细java中的ClassLoader详解 博主讲得通俗易懂
  • 关于JVM的的知识,推荐一本书《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》 周志明,我后期也会整理相关知识,敬请期待

2.2 获取配置文件

// 第一阶段:MyBatis的初始化阶段
String resource = "mybatis-config.xml";
// 得到配置文件的输入流
InputStream inputStream = null;
try {
    inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
    e.printStackTrace();
}
  • 我们可以看到调用了Resources#getResourceAsStream(resource)去获取配置文件的信息,调用重载getResourceAsStream()方法,进入Resources这个类中进行方法的调用。

Resources

  public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    // 去加载我们写的mybatis-config.xml 文件
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
	// 没有找到,资源不存在
    if (in == null) {
      throw new IOException("Could not find resource " + resource);
    }
    return in;
  }
  • 到这我们可以看到他调用了classLoaderWrapper的方法,我们来看看这个类是啥?
  • ClassLoaderWrapper 类中封装了五种类加载器,而 Resources 类又对 ClassLoaderWrapper 类进行了一些封装。

ClassLoaderWrapper

public class ClassLoaderWrapper {

  ClassLoader defaultClassLoader;
    
  ClassLoader systemClassLoader;

  ClassLoaderWrapper() {
    try {
   	 // AppClassLoader 
      systemClassLoader = ClassLoader.getSystemClassLoader();
    } catch (SecurityException ignored) {
      // AccessControlException on Google App Engine
    }
  }

  • 我们可以看到在ClassLoaderWrapper的构造器中,对systemClassLoader进行初始化,而ClassLoader.getSystemClassLoader()的结果就是我们上文介绍的AppClassLoader

下面一个小案例,来证实我们的猜想?
image.png
到这我们需要注意一下getClassLoaders(classLoader))方法,打个断点,调试一手
ClassLoaderWrapper


public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    return getResourceAsStream(resource, getClassLoaders(classLoader));
  }

/**
 * 获取多个ClassLoader,这一步是必须的,因为,我们就是从这个加载器中获取资源的流的
 *五种类加载器:自己传入的、默认的类加载器、当前线程的类加载器、本类的类加载器、系统类加载器
 * @param classLoader 我们定义的自己的类加载器
 * @return 类加载器的数组
 */
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
            classLoader,
            defaultClassLoader,
            Thread.currentThread().getContextClassLoader(),
            getClass().getClassLoader(),
            systemClassLoader};
}

  • 这五种类加载器依次是:· 作为参数传入的类加载器,可能为 null;· 系统默认的类加载器,如未设置则为 null;· 当前线程的线程上下文中的类加载器;· 当前对象的类加载器;· 系统类加载器,在 ClassLoaderWrapper的构造方法中设置。以上五种类加载器的优先级由高到低。在读取类文件时,依次到上述五种类加载器中进行寻找,只要某一次寻找成功即返回结果。

image.png

  • 用一组 ClassLoader去找到我们写的mybatis-conf.xml文件,一般情况下,类加载器会将名称转换为文件名,然后从文件系统中读取该名称的类文件,因此,类加载器具有读取外部资源的能力,这里要借助的正是类加载器的这种能力。
/**
 * 从一个ClassLoader中获取资源的流,这就是我们的目的
 *
 * @param resource    资源的地址
 * @param classLoader 类加载器
 * @return 流
 */
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    for (ClassLoader cl : classLoader) {
        if (null != cl) {

            // 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
            if (null == returnValue) {
                returnValue = cl.getResourceAsStream("/" + resource);
            }

            if (null != returnValue) {
                return returnValue;
            }
        }
    }
    return null;
}
  • getResourceAsStream 方法会依次调用传入的每一个类加载器的getResourceAsStream方法来尝试获取配置文件的输入流

ClassLoader

    public InputStream getResourceAsStream(String name) {
        // 找到文件
        URL url = getResource(name);
        try {
            if (url == null) {
                return null;
            }
        
            URLConnection urlc = url.openConnection();
            InputStream is = urlc.getInputStream();
            if (urlc instanceof JarURLConnection) {
                JarURLConnection juc = (JarURLConnection)urlc;
                JarFile jar = juc.getJarFile();
                synchronized (closeables) {
                    if (!closeables.containsKey(jar)) {
                        closeables.put(jar, null);
                    }
                }
            } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
                synchronized (closeables) {
                    closeables.put(is, null);
                }
            }
            return is;
        } catch (IOException e) {
            return null;
        }
    }
  • 我们来看看getResource方法吧,相信你刚才看了文章,接下来看你理解没有刚才的知识,这里需要了解一下Java虚拟机的双亲委派机制:简单来说就是先委派自己的父类来加载文件,如果父类没有,尝试子类自己加载文件。
    public URL getResource(String name) {
        URL url;
        // 父类加载器能够找到该文件,由前面我们知道AppClassLoader的父类加载器是ExtClassLoader
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            // 通过双亲委派机制找到文件
            url = getBootstrapResource(name);
        }
    	// 没有的话
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }
  • 由前面我们知道AppClassLoader的父类加载器是ExtClassLoader

image.png

  • ExtClassLoader的父类加载器为空,所以通过双亲委派机制去寻找该文件,相信我,后面你还会遇到的。

image.png

  • 当前类加载器(一般是appclassloader)会让父类去加载,父类找不到再通过子类自身findResource(name)方法来找资源

URLClassLoader

 public URL findResource(final String name) {
        /*
         * The same restriction to finding classes applies to resources
         */
        URL url = AccessController.doPrivileged(
            new PrivilegedAction<URL>() {
                public URL run() {
                    return ucp.findResource(name, true);
                }
            }, acc);

        return url != null ? ucp.checkURL(url) : null;
    }
  • URLClassLoader这个类加载器用于从引用JAR文件和目录的url的搜索路径加载类和资源。任何以“/”结尾的URL都被认为指向目录。否则,URL被假定为引用一个JAR文件,该文件将在需要时打开。 创建URLClassLoader实例的线程的AccessControlContext将在随后加载类和资源时使用,默认情况下,被加载的类只被授予访问创建URLClassLoader时指定的url的权限。
  • Java ClassLoader findResource() method with example
  • AccessController.doPrivileged方法是一个native方法,无法通过IDE进去调试

URLClassPath




public URL findResource(String var1, boolean var2) {
         // 先去缓存查询一下
        int[] var4 = this.getLookupCache(var1);
        Loader var3;
         // 这里有点不懂,有大神可以讲解?
        for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
            URL var6 = var3.findResource(var1, var2);
            if (var6 != null) {
                return var6;
            }
        }

        return null;
    }
  • 找到了文件的URL路径,返回

image.png

  • 获取到了URL连接
    public InputStream getResourceAsStream(String name) {
        // 找到文件
        URL url = getResource(name);
        try {
            if (url == null) {
                return null;
            }
            // 打开连接
            URLConnection urlc = url.openConnection();
            // 获取流数据
            InputStream is = urlc.getInputStream();
            // jar包连接
            if (urlc instanceof JarURLConnection) {
                JarURLConnection juc = (JarURLConnection)urlc;
                JarFile jar = juc.getJarFile();
                synchronized (closeables) {
                    if (!closeables.containsKey(jar)) {
                        closeables.put(jar, null);
                    }
                }
            } 
            // 文件连接    
            else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
                synchronized (closeables) {
                    closeables.put(is, null);
                }
            }
            return is;
        } catch (IOException e) {
            return null;
        }
    }

到这我们文件的解析就完毕了,到这我们就拿到了文件流数据,下篇文章我们来解释SqlSessionFactory的初始化。

三 扩展

说到输入/输出,首先想到的就是对磁盘文件的读写。在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的读操作,因此,Mybatis的io包中提供对磁盘文件读操作的支持。

3.1 VFS

磁盘文件系统分为很多种,如 FAT、VFAT、NFS、NTFS等。不同文件系统的读写操作各不相同。VFS(Virtual File System)作为一个虚拟的文件系统将各个磁盘文件系统的差异屏蔽了起来,提供了统一的操作接口。这使得上层的软件能够用单一的方式来跟底层不同的文件系统沟通。

  • MyBatis的 io包中 VFS的作用是从应用服务器中找寻和读取资源文件,这些资源文件可能是配置文件、类文件等。首先我们来看看VFS的抽象类信息?
 private static final Log log = LogFactory.getLog(VFS.class);

    public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };

    public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<>();



    private static class VFSHolder {
        // 最终指定的实现类
        static final VFS INSTANCE = createVFS();

        /**
         * 给出一个VFS实现。单例模式
         * @return VFS实现
         */
        static VFS createVFS() {
            // 所有VFS实现类的列表。
            List<Class<? extends VFS>> impls = new ArrayList<>();
            // 列表中先加入用户自定义的实现类。因此,用户自定义的实现类优先级高
            impls.addAll(USER_IMPLEMENTATIONS);
            impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));

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

在 VFSHolder类的 createVFS方法中,先组建一个 VFS实现类的列表,然后依次对列表中的实现类进行校验。第一个通过校验的实现类即被选中,在组建列表时,用户自定义的实现类放在了列表的前部,这保证了用户自定义的实现类具有更高的优先级。
image.png

3.1.1 DefaultVFS

DefaultVFS 作为默认的 VFS 实现类,其 isValid 函数恒返回 true。因此,只要加载DefaultVFS类,它一定能通过 VFS类中 VFSHolder单例中的校验,并且在进行实现类的校验时 DefaultVFS排在整个校验列表的最后,因此,DefaultVFS成了所有 VFS实现类的保底方案,即最后一个验证,但只要验证一定能通过。
方法:
image.png

  1. list(URL,String):列出指定 url下符合条件的资源名称。
  2. listResources(JarInputStream,String):列出给定 jar包中符合条件的资源名称。
  3. findJarForResource(URL):找出指定路径上的 jar包,返回 jar包的准确路径。
  4. getPackagePath(String):将 jar包名称转为路径。
  5. isJar:判断指定路径上是否是 jar包。

3.1.2 JBoss6VFS

  • JBoss是一个基于 J2EE的开放源代码的应用服务器,JBoss6是 JBoss中的一个版本。JBoss6VFS即为借鉴 JBoss6设计的一套 VFS实现类。
  • 在 JBoss6VFS中主要存在两个内部类。· VirtualFile:仿照 JBoss中的 VirtualFile类设计的一个功能子集;· VFS:仿照 JBoss中的 VFS类设计的一个功能子集。阅读 VirtualFile和 VFS中的方法便可以发现,这些方法中都没有实现具体的操作,而是调用 JBoss中的相关方法。
  • 在 JBoss6VFS类中,两个内部类 VirtualFile和 VFS都是代理类,只负责完成将相关操作转给被代理类的工作。那么,要想使 JBoss6VFS类正常工作,必须确保被代理类存在。
          /**
         * 获取相关的路径名
         * @param parent 父级路径名
         * @return 相关路径名
         */
        String getPathNameRelativeTo(VirtualFile parent) {
            try {
                return invoke(getPathNameRelativeTo, virtualFile, parent.virtualFile);
            } catch (IOException e) {
                // This exception is not thrown by the called method
                log.error("This should not be possible. VirtualFile.getPathNameRelativeTo() threw IOException.");
                return null;
        }


		static VirtualFile getChild(URL url) throws IOException {
            Object o = invoke(getChild, VFS, url);
            return o == null ? null : new VirtualFile(o);
        }



         

这里使用代理模式来 VirtualFile内部类是 JBoss中 VirtualFile的静态代理类,同样VFS也是JBoss的静态代理类,他是如何确定来保证代理类的呀?

  /** Find all the classes and methods that are required to access the JBoss 6 VFS. */
    /**
     * 初始化JBoss6VFS类。主要是根据被代理类是否存在来判断自身是否可用
     */
    protected static synchronized void initialize() {
        if (valid == null) {
            // 首先假设是可用的
            valid = Boolean.TRUE;

            // 校验所需要的类是否存在。如果不存在,则valid设置为false
            VFS.VFS = checkNotNull(getClass("org.jboss.vfs.VFS"));
            VirtualFile.VirtualFile = checkNotNull(getClass("org.jboss.vfs.VirtualFile"));

            // 校验所需要的方法是否存在。如果不存在,则valid设置为false
            VFS.getChild = checkNotNull(getMethod(VFS.VFS, "getChild", URL.class));
            VirtualFile.getChildrenRecursively = checkNotNull(getMethod(VirtualFile.VirtualFile,
                    "getChildrenRecursively"));
            VirtualFile.getPathNameRelativeTo = checkNotNull(getMethod(VirtualFile.VirtualFile,
                    "getPathNameRelativeTo", VirtualFile.VirtualFile));

            // 判断以上所需方法的返回值是否和预期一致。如果不一致,则valid设置为false
            checkReturnType(VFS.getChild, VirtualFile.VirtualFile);
            checkReturnType(VirtualFile.getChildrenRecursively, List.class);
            checkReturnType(VirtualFile.getPathNameRelativeTo, String.class);
        }
    }

在初始化方法中,会尝试从 JBoss 的包中加载和校验所需要的类和方法,最后,还通过返回值对加载的方法进行了进一步的校验,而在以上的各个过程中,只要发现加载的类、方法不存在或者返回值发生了变化,则认为 JBoss 中的类不可用,在这种情况下,checkNotNull方法和 checkReturnType方法中会调用 setInvalid 方法将 JBoss6VFS的 valid字段设置为 false,表示 JBoss6VFS类不可用。

3.2 ResolverUtil工具类

ResolverUtil是一个工具类,主要功能是完成类的筛选,这些筛选条件可以是:·类是否是某个接口或类的子类;类是否具有某个注解。

    /**
     * 筛选出指定路径下符合一定条件的类
     * @param test 测试条件
     * @param packageName 路径
     * @return ResolverUtil本身
     */
    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;
    }



/**
     * 判断一个类文件是否满足条件。如果满足则记录下来
     * @param test 测试条件
     * @param fqn 类文件全名
     */
    @SuppressWarnings("unchecked")
    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属性中
                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());
        }
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长安不及十里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值