美文网首页Java
Spring 源码之实现Class对象的提取

Spring 源码之实现Class对象的提取

作者: 该用户已秃头 | 来源:发表于2021-03-09 15:53 被阅读0次

获取Class类的集合

获取某个包下的类集合 代码如下 . 主要是三步

  1. 获取类加载器: 通过Thread.currentThread().getContextClassLoader() 来获取
  2. 通过类加载器获取到加载的资源信息 , 是通过classLoader.getResource获取, 传入包路径, 注意要把. , 转化为"/".
  3. 获取类的集合: 主要是 过滤出文件类型, 目前只处理file类型的协议, 再获取包下的实际绝对路径, 最后是通过绝对路径, 去递归获取类的集合 .

完整代码如下

/**
     * 获取某个包下的类集合 :
     * 1\. 获取到类加载器:
     * 目的:   获取项目发布的实际路径 . 不同的系统表示路径的方法不同. 如果是war或jar包就找不到jar包.
     * 类加载器: ClassLoader
     * <p>
     * 2\. 通过类加载器获取到加载的资源信息
     * 3\. 根据不同的资源类型, 采取不同的方式获取资源的集合
     *
     * @param packageName 包名
     * @return 类集合
     */
    public static Set<Class<?>> extractPackageClass(String packageName) {
        // 1\. 获取到类加载器
        ClassLoader classLoader = getClassLoader();
        // 2\. 通过类加载器获取到加载的资源信息
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (url == null) {
            log.error(" 无法获取到资源, 从此包中 : " + packageName);
            return null;
        }
        // 3\. 根据不同的资源类型(目前只解析file类型的), 采取不同的方式获取资源的集合
        Set<Class<?>> classSet = null;
        // 过滤出文件类型资源
        if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)) {
            classSet = new HashSet<Class<?>>();
            // 获取package的实际路径
            File packageDirectory = new File(url.getPath());
            //  递归获取目标package 里面的所有class文件(包括子package里的class文件)
            extractClassFile(classSet, packageDirectory, packageName);
        }
        // 返回所有的类实例
        return classSet;
    }
    /**
     * 递归获取目标package里面的所有class文件(包括子package里的class文件)
     *
     * @param emptyClassSet 装载目标类的集合
     * @param fileSource    目录或文件
     * @param packageName   包名
     */
    private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
        if (!fileSource.isDirectory()) {
            //  如果资源不是文件夹, 那么直接结束
            return;
        }
        // 获取一个文件夹下的所有文件或者文件夹, 不包含子文件夹
        File[] files = fileSource.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    // 筛选出只含有文件夹的资源
                    return true;
                } else {
                    //获取文件的绝对值路径
                    String absolutePath = file.getAbsolutePath();
                    if (absolutePath.endsWith(".class")) {
                        // 如果是class文件, 加载到内存中
                        addToClassSet(absolutePath);
                    }
                }
                return false;
            }

            // 根据class文件的绝对值路径, 获取并生成class对象, 并放入classSet中
            private void addToClassSet(String absolutePath) {
                // 1\. 从class文件的绝对值路径里提取出包含了package的类名
                // 如absolutePath D:\mycode\spring_study\simpleframework\src\main\java\org\simpleframework\core\annotation\Component.class
                // 如  org.simpleframework.core.annotation.Component

                // 把\ 换成 .
                absolutePath = absolutePath.replace(File.separator, ".");
                //  例如传入的包名为org.simpleframework.core 代表从 org.simpleframework.core 开始截取
                String className = absolutePath.substring(absolutePath.indexOf(packageName));
                // 去掉 末尾的class
                className = className.substring(0, className.lastIndexOf("."));

                // 2\. 通过反射机制, 获取对应的class对象并加入到classSet里
                Class<?> targetClass = loadClass(className);
                // 把加载的类放入集合中
                emptyClassSet.add(targetClass);

            }
        });

        // 迭代遍历之前, 先判断是否为空
        if (files != null) {
            for (File f : files) {
                // 递归调用
                extractClassFile(emptyClassSet, f, packageName);
            }
        }
    }

    /**
     * 获取class 对象
     *
     * @param className class全名 = package+类名
     * @return
     */
    public static Class<?> loadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            // 加载类异常
            log.error("load class error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取当前的ClassLoader
     * 程序是通过线程执行的, 获取当前执行的方法的线程, 便能通过线程属的类加载器获取程序资源信息
     *
     * @return
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

单元测试

pom中加入如下的依赖

       <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>

在test包中, 编写如下的测试类

public class ClassUtilTest {

    // 表明测试用例的含义
    @DisplayName("extractPackageClassTest 提取目标类方法")
    @Test
    public void extractPackageClassTest(){
        Set<Class<?>> classes = ClassUtil.extractPackageClass("demo.annotation");
           if (classes != null) {
            for (Class<?> aClass : classes) {
                System.out.println(aClass);
            }
        }
        Assertions.assertEquals(5, classes.size());
    }
}

在我当前的项目中, demo.annotation包下有五个类, 因此在上面的断言中, 我断言了5个.

控制台打印如下 , 测试通过 , 并且成功打印获取了如下的五个类.

作者:Java持续实践
链接:https://juejin.cn/post/6936497961983541279
来源:掘金

相关文章

网友评论

    本文标题:Spring 源码之实现Class对象的提取

    本文链接:https://www.haomeiwen.com/subject/phkbqltx.html