You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java ClassLoader运行Jar包时获取类输入流为空问题解决

Jar包运行时获取指定包下所有类的解决方案

我编写了一段用于获取指定包下所有类的Java代码,在IDE中运行正常,但使用java -jar命令运行应用时,InputStream为空,代码如下:

public static Set<Class> findAllClassesUsingClassLoader(String packageName) throws Exception {
        InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(packageName.replaceAll("[.]", "/"));
        System.out.println(packageName);
        String text = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
        System.out.println(text);
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        return reader.lines().filter(line -> line.endsWith(".class")).map(line -> {
            try {
                System.out.println("HERE");
                return getClass(line, packageName);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toSet());
    }

    public static Class getClass(String className, String packageName) throws Exception {
        try {
            return Class.forName(packageName + "." + className.substring(0, className.lastIndexOf('.')));
        } catch (ClassNotFoundException e) {
            throw new Exception("Class " + className + " not found!");
        }
    }

问题原因

IDE环境中,类文件以散列的文件形式存储在文件系统目录下,getResourceAsStream可以读取到对应包目录的输入流;但代码打包成Jar包后,Jar是压缩归档文件,包目录并非真实的文件系统目录,因此getResourceAsStream无法获取有效输入流,返回null。

修改后的代码

通过区分文件系统和Jar包两种运行环境,使用类加载器枚举资源并分别处理,即可解决该问题:

import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.stream.Collectors;

public class ClassScanner {

    public static Set<Class<?>> findAllClasses(String packageName) throws Exception {
        Set<Class<?>> classes = new HashSet<>();
        String path = packageName.replace('.', '/');
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // 枚举所有匹配的资源URL
        Enumeration<URL> resources = classLoader.getResources(path);
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            String protocol = resource.getProtocol();

            if ("file".equals(protocol)) {
                // 文件系统环境:遍历目录加载类
                String filePath = URLDecoder.decode(resource.getFile(), StandardCharsets.UTF_8.name());
                scanFileSystemClasses(new java.io.File(filePath), packageName, classes);
            } else if ("jar".equals(protocol)) {
                // Jar包环境:遍历Jar条目加载类
                try (JarInputStream jarStream = new JarInputStream(resource.openStream())) {
                    JarEntry entry;
                    while ((entry = jarStream.getNextJarEntry()) != null) {
                        String entryName = entry.getName();
                        // 筛选当前包及子包下的.class文件
                        if (entryName.startsWith(path) && entryName.endsWith(".class")) {
                            String className = entryName.replace('/', '.').substring(0, entryName.length() - 6);
                            try {
                                classes.add(Class.forName(className));
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
        return classes;
    }

    private static void scanFileSystemClasses(java.io.File dir, String packageName, Set<Class<?>> classes) throws Exception {
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 递归遍历目录下的文件和子目录
        for (java.io.File file : Objects.requireNonNull(dir.listFiles())) {
            if (file.isDirectory()) {
                scanFileSystemClasses(file, packageName + "." + file.getName(), classes);
            } else if (file.getName().endsWith(".class")) {
                String className = packageName + "." + file.getName().substring(0, file.getName().length() - 6);
                try {
                    classes.add(Class.forName(className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

关键修改点

  • 使用Thread.currentThread().getContextClassLoader()获取类加载器,适配更多运行场景,比系统类加载器更灵活
  • 通过getResources枚举所有匹配的资源URL,区分file和jar协议分别处理
  • 文件系统环境下递归遍历目录,自动处理子包类加载;Jar包环境下遍历Jar条目,精准筛选目标类文件
  • 新增URL解码逻辑,避免路径含特殊字符导致的加载失败问题

内容的提问来源于stack exchange,提问作者Paolo Mossini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 08:12:45