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
相关产品推荐
相关产品推荐

