Java 扫描并加载包路径下 class 文件

本贴最后更新于 2650 天前,其中的信息可能已经水流花落

背景

用过 spring 框架之后,有个指定扫描包路径,然后自动实例化一些 bean,这个过程还是比较有意思的,抽象一下,即下面三个点

  1. 如何扫描包路径下所有的 class 文件
  2. 如何扫描 jar 包中对应包路径下所有的 class 文件
  3. 如何加载 class 文件

实现

目标

我们的目标是给定一个包路径,然后加载这个包路径下的所有 class

考虑两种场景

  1. 包路径为依赖第三方 jar 包中的
  2. 包路径为自己的业务代码中的 --》 常见的一种是业务代码会编译成 class 文件,即扫描文件

实现

针对上面两种场景,分开说明

1. 扫描文件

实现流程比较清晰:

  • 根据包名,获取绝对地址,直接进入包对应的目录
  • 扫描目录下所有文件
    • 加载所有的 class 文件;
    • 如果是目录,迭代遍历目录下的 class 文件
  • 加载 class 文件

获取包对应的绝对地址,这里先不说,下面直接给出进入目录,加载所有 class 文件的代码

/**
 * 扫描包路径下的所有class文件
 *
 * @param pkgName 包名
 * @param pkgPath 包对应的绝对地址
 * @param classes 保存包路径下class的集合
 */
private static void findClassesByFile(String pkgName, String pkgPath, Set<Class<?>> classes) {
    File dir = new File(pkgPath);
    if (!dir.exists() || !dir.isDirectory()) {
        return;
    }


    // 过滤获取目录,or class文件
    File[] dirfiles = dir.listFiles(pathname -> pathname.isDirectory() || pathname.getName().endsWith("class"));


    if (dirfiles == null || dirfiles.length == 0) {
        return;
    }


    String className;
    Class clz;
    for (File f : dirfiles) {
        if (f.isDirectory()) {
            findClassesByFile(pkgName + "." + f.getName(),
                    pkgPath + "/" + f.getName(),
                    classes);
            continue;
        }


        // 获取类名,干掉 ".class" 后缀
        className = f.getName();
        className = className.substring(0, className.length() - 6);

        // 加载类
        clz = loadClass(pkgName + "." + className);
        if (clz != null) {
            classes.add(clz);
        }
    }
}

2. 扫描 jar

流程和上面一样,实现上稍稍有些区别,由之前的扫描文件变成遍历 JarFile

/**
 * 扫描包路径下的所有class文件
 *
 * @param pkgName 包名
 * @param jar     jar文件
 * @param classes 保存包路径下class的集合
 */
private static void findClassesByJar(String pkgName, JarFile jar, Set<Class<?>> classes) {
    String pkgDir = pkgName.replace(".", "/");


    Enumeration<JarEntry> entry = jar.entries();

    JarEntry jarEntry;
    String name, className;
    Class<?> claze;
    while (entry.hasMoreElements()) {
        jarEntry = entry.nextElement();

        name = jarEntry.getName();
        if (name.charAt(0) == '/') {
            name = name.substring(1);
        }


        if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(".class")) {
            // 非指定包路径, 非class文件
            continue;
        }


        // 去掉后面的".class", 将路径转为package格式
        className = name.substring(0, name.length() - 6);
        claze = loadClass(className.replace("/", "."));
        if (claze != null) {
            classes.add(claze);
        }
    }
}

3. 扫描包

上面是具体的扫 class 文件的过程,那么如何根据包获取对应的 jarFile or 包对应的绝对地址呢?

主要利用的是 XXX.class.getClassLoader().getResources(package), 具体如下

/**
 * 扫描包路径下所有的class文件
 *
 * @param pkg
 * @return
 */
public static Set<Class<?>> getClzFromPkg(String pkg) {
    Set<Class<?>> classes = new LinkedHashSet<>();

    String pkgDirName = pkg.replace('.', '/');
    try {
        Enumeration<URL> urls = PkgUtil.class.getClassLoader().getResources(pkgDirName);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            String protocol = url.getProtocol();
            if ("file".equals(protocol)) {// 如果是以文件的形式保存在服务器上
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");// 获取包的物理路径
                findClassesByFile(pkg, filePath, classes);
            } else if ("jar".equals(protocol)) {// 如果是jar包文件
                JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                findClassesByJar(pkg, jar, classes);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    return classes;
}

4. 类加载

这个还是比较简单的,一搜一大把,直接贴出

private static Class<?> loadClass(String fullClzName) {
    try {
        return Thread.currentThread().getContextClassLoader().loadClass(fullClzName);
    } catch (ClassNotFoundException e) {
        log.error("load class error! clz: {}, e:{}", fullClzName, e);
    }
    return null;
}

测试

要愉快的测试这一功能,你可以选择一个 jar 包,如 org.slf4j, 然后自己创建几个测试类,包名也是已 org.slf4j 开头,然后调用上面的方法

Class<?> set = PkgUtil.getClzFromPkg("org.slf4j");

因为这个工具类我是放在 quick-mvc 工程的,所以就直接使用了我定义的包 com.hust.hui,因为没啥通用性,就给出本机测试的演示图好了

演示图

其他

源码地址: PkgUtil.java

个人博客:一灰的个人博客

个人信息

公众号获取更多:

个人信息

  • 包扫描
    1 引用
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...