如何动态加载Jar文件到Java项目并解决类找不到异常?
首先,你遇到的java.lang.ClassNotFoundException核心原因和修正方案如下:
1. Jar文件格式错误
URLClassLoader只能加载编译后的.class字节码文件,而你的Jar中存放的是MyMod.java源文件,这是导致找不到类的根本问题。
- 修正方式:先将
MyMod.java编译为MyMod.class,再重新打包Jar,正确的Jar结构应为:file.jar |_ mymod (文件夹) |_ MyMod.class
2. 代码中的其他问题修正
即使Jar格式正确,现有代码还有几个影响类加载的问题需要调整:
(1)ClassLoader父加载器未正确设置
要支持Jar中子类QueenBee继承项目中的父类Bee,必须让URLClassLoader能访问项目的类路径。默认URLClassLoader的父加载器是系统类加载器,可能无法访问你的项目类,因此需要指定当前类的加载器作为父加载器:
URLClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()}, this.getClass().getClassLoader());
(2)文件扩展名判断不严谨
getFileExtension方法可能存在大小写(比如Jar文件是.JAR)或多扩展名(比如test.jar.bak)判断错误,可替换为更可靠的实现:
private static String getFileExtension(File file) { String name = file.getName(); int lastDotIndex = name.lastIndexOf('.'); return lastDotIndex == -1 ? "" : name.substring(lastDotIndex + 1).toLowerCase(); }
(3)未处理IOException
file.toURI().toURL()会抛出IOException,需要在try-catch中捕获或声明抛出。
(4)重复创建ClassLoader的资源浪费
循环中每次创建新的URLClassLoader会消耗不必要的资源,建议收集所有Jar的URL后统一创建一个ClassLoader。
修正后的完整代码示例
import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.Objects; public class JarLoader { public static void loadMods() { String homeDir = System.getProperty("user.home"); String modsPath = homeDir + File.separator + "AppData" + File.separator + "Roaming" + File.separator + ".minecraft" + File.separator + "mods"; File modsFolder = new File(modsPath); if (!modsFolder.exists() || !modsFolder.isDirectory()) { return; } List<URL> jarUrls = new ArrayList<>(); // 收集所有Jar文件的URL for (File file : Objects.requireNonNull(modsFolder.listFiles())) { if (getFileExtension(file).equals("jar")) { try { jarUrls.add(file.toURI().toURL()); } catch (MalformedURLException e) { e.printStackTrace(); } } } // 统一创建ClassLoader,指定父加载器为当前类的加载器 try (URLClassLoader classLoader = new URLClassLoader(jarUrls.toArray(new URL[0]), JarLoader.class.getClassLoader())) { // 加载并调用MyMod类的scanMods方法 Class<?> modClass = classLoader.loadClass("mymod.MyMod"); Method scanMethod = modClass.getDeclaredMethod("scanMods"); scanMethod.invoke(modClass); // 静态方法也可传null作为invoke参数 } catch (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } private static String getFileExtension(File file) { String name = file.getName(); int lastDotIndex = name.lastIndexOf('.'); return lastDotIndex == -1 ? "" : name.substring(lastDotIndex + 1).toLowerCase(); } }
继承场景的支持说明
只要URLClassLoader的父加载器设置正确(能访问项目中的Bee类),Jar中的QueenBee类无需包含Bee类,就能正常继承并被加载调用——这符合Java的双亲委派模型:ClassLoader会先通过父加载器查找Bee类,父加载器找不到才会尝试自己加载。
是否需要更换URLClassLoader?
不需要,URLClassLoader完全能满足你的需求。如果是Java 9及以上版本,也可以考虑使用ModuleLayer实现模块化加载,但对于你的场景,URLClassLoader更简单直接,修正上述问题后即可正常工作。
内容的提问来源于stack exchange,提问作者Francesco Limosani

