当前位置: 首页 > 知识库问答 >
问题:

在运行时加载jar会导致NoClassDefFoundError/ClassNotFoundException

祁建明
2023-03-14

摘要:从正在运行的Java程序加载jar会导致由类间依赖(例如导入语句)引起的ClassNotFoundExctive引起的NoClassDefFoundError。我如何绕过它?

更详细的问题是:

我试图通过自己的Java程序将jar文件(我们称之为“服务器”)以编程方式加载到Java虚拟机中(我们称之为“ServerAPI”),并使用扩展和其他一些技巧来修改服务器的行为并与之交互。ServerAPI依赖于服务器,但如果服务器不存在,ServerAPI仍然必须能够从网站上运行和下载服务器。

为了避免由于ServerAPI加载而不满足其对服务器的依赖而导致的错误,我制作了一个启动器——让我们称之为“启动器”——它旨在下载服务器并根据需要设置ServerAPI,然后加载服务器和ServerAPI,然后运行ServerAPI。

然而,当我试图从Launcher加载JAR时,会出现错误,因为类加载器无法解析它所加载的类所依赖的文件中的其他类。简而言之,如果我试图加载类A,如果A导入B,它将抛出一个错误,因为我还没有加载B。然而,如果B也导入了A,我就卡住了,因为我不知道如何一次加载两个类,或者如何在没有JVM运行其验证的情况下加载一个类。

为什么所有的限制都让我遇到了这个问题:

我试图修改和添加服务器的行为,但由于复杂的法律原因,我无法直接修改程序,因此我创建了ServerAPI,它依赖于并可以从外部调整服务器的行为。

然而,由于更复杂的法律原因,服务器和服务器API不能简单地一起下载。Launcher(见上文)必须使用ServerAPI下载,然后Launcher需要下载服务器。最后,可以使用服务器作为依赖项来运行ServerAPI。这就是为什么这个问题如此复杂。

这个问题也将适用于项目的后续部分,这将涉及一个基于插件的API接口,该接口需要能够在运行时从jar文件加载和卸载插件。

我已经对这个问题进行了研究:

我通读了一遍,但没有得到以下方面的帮助:

  • 这个问题只解决了单个方法的问题,没有解决类间依赖性错误
  • 这个问题是行不通的,因为我无法在每次加载或卸载jar时关闭并重新启动程序(主要针对我简要提到的插件部分)
  • 这个问题只适用于程序启动时存在依赖关系的情况
  • 这个问题与#2有同样的问题
  • 这个问题与#3有同样的问题
  • 从这篇文章中,我了解了隐藏的loadClass(String,boolean)方法,但尝试使用true和false值并没有帮助
  • 这个问题与#1有同样的问题

等等。什么都没用。

//编辑:我迄今为止的尝试:

我已经尝试过使用URLClassLoaders从JarFile中使用JarEntries加载jar,类似于这个问题。我使用并调用URLClassLoaderloadClass(String)方法,并创建了一个扩展URLClassLoader的类,这样我就可以利用loadClass(String,boolean resolve)来强制ClassLoader解析它加载的所有类。两方面,我都犯了同样的错误:

I couldn't find the class in the JarEntry!
entry name="org/apache/logging/log4j/core/appender/db/jpa/converter/ContextMapAttributeConverter.class"
class name="org.apache.logging.log4j.core.appender.db.jpa.converter.ContextMapAttributeConverter"
java.lang.NoClassDefFoundError: javax/persistence/AttributeConverter
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:455)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:367)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at Corundum.launcher.CorundumClassLoader.load(CorundumClassLoader.java:52)
    at Corundum.launcher.CorundumLauncher.main(CorundumLauncher.java:47)
Caused by: java.lang.ClassNotFoundException: javax.persistence.AttributeConverter
    at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 12 more

//结束编辑

//编辑2:

下面是我用来加载一个类并试图解析它的代码示例。这是在我创建的一个类中,该类扩展了URLClassLoader。在以类开头的行中

public boolean load(ClassLoadAction class_action, FinishLoadAction end_action) {
    // establish the jar associated with this ClassLoader as a JarFile
    JarFile jar;
    try {
        jar = new JarFile(jar_path);
    } catch (IOException exception) {
        System.out.println("There was a problem loading the " + jar_path + "!");
        exception.printStackTrace();
        return false;
    }

    // load each class in the JarFile through its JarEntries
    Enumeration<JarEntry> entries = jar.entries();

    if (entries.hasMoreElements())
        for (JarEntry entry = entries.nextElement(); entries.hasMoreElements(); entry = entries.nextElement())
            if (!entry.isDirectory() && entry.getName().endsWith(".class"))
                try {
                    /* this "true" in the line below is the whole reason this class is necessary; it makes the URLClassLoader this class extends "resolve" the class,
                     * meaning it also loads all the classes this class refers to */
                    Class<?> clazz = loadClass(entry.getName().substring(0, entry.getName().length() - 6).replaceAll("/", "."), true);
                    class_action.onClassLoad(this, jar, clazz, end_action);
                } catch (ClassNotFoundException | NoClassDefFoundError exception) {
                    try {
                        close();
                    } catch (IOException exception2) {
                        System.out.println("There was a problem closing the URLClassLoader after the following " + exception2.getClass().getSimpleName() + "!");
                        exception.printStackTrace();
                    }
                    try {
                        jar.close();
                    } catch (IOException exception2) {
                        System.out.println("There was a problem closing the JarFile after the following ClassNotFoundException!");
                        exception.printStackTrace();
                    }
                    System.out.println("I couldn't find the class in the JarEntry!\nentry name=\"" + entry.getName() + "\"\nclass name=\""
                            + entry.getName().substring(0, entry.getName().length() - 6).replaceAll("/", ".") + "\"");
                    exception.printStackTrace();
                    return false;
                }

    // once all the classes are loaded, close the ClassLoader and run the plugin's main class(es) load() method(s)
    try {
        jar.close();
    } catch (IOException exception) {
        System.out.println("I couldn't close the URLClassLoader used to load this jar file!\njar file=\"" + jar.getName() + "\"");
        exception.printStackTrace();
        return false;
    }

    end_action.onFinishLoad(this, null, class_action);
    System.out.println("loaded " + jar_path);
    // TODO TEST
    try {
        close();
    } catch (IOException exception) {
        System.out.println("I couldn't close the URLClassLoader used to load this jar file!\njar file=\"" + jar_path + "\"");
        exception.printStackTrace();
        return false;
    }
    return true;
}

//结束编辑2

我意识到必须有一个简单的解决办法,但就我的一生而言,我似乎找不到它。任何帮助都会让我永远感激。非常感谢。


共有1个答案

徐景明
2023-03-14

令人尴尬的是,我发现答案是错误消息说的是实话。javax.persistence.属性转换器,加载程序声称的类不存在,不在jar中。

我修复了这个问题,只加载了主类和ClassLoader所有引用类,基本上加载了jar中程序中使用的所有类,这就是我所需要的。

现在,我可以发誓,我之前检查过这个,发现了那个班级;我想当我检查时,我一定是检查了类的Apache开源存储库,而不是实际的服务器。我不记得了。

在任何情况下,AttributeConverter都会丢失。我不知道他们是如何或为什么设法编译一个缺少依赖项的jar的,但我猜他们的主要进程从来没有使用过代码的这一部分,所以它从来没有抛出错误。

很抱歉浪费了大家的时间。。。包括我自己。这个问题我已经纠结了一段时间了。

这个故事的寓意:

如果你试图加载一个可执行的jar,除非你真的必须加载,否则不要在jar中加载所有的类。只需加载主类;这将加载程序需要运行的所有内容。

//编辑:

我现在开始出现同样的错误,但直到我尝试从加载的类中调用一个方法时才会出现。这个问题显然仍然悬而未决。请投反对票,不要理会这个答案。

 类似资料:
  • 附上一个拉链,里面有三个文件夹。Shmer示例,ShimimDriver和ShimimroidInstrumentDriver。Shimime示例是主程序,并依赖于其他两个项目作为库。 我启动AndroidStudio 1.1并将“ShimmerExample”作为eclipse项目导入。导入摘要。txt在下面。 该项目构建良好,并加载到android设备上。启动应用程序后,我得到该类位于vecm

  • 问题内容: 我试图在运行时将jar文件添加到classpath。我用这个代码 系统输出打印此URL: 我正在仔细检查此路径,此jar存在。即使这个测试也显示com.mysql.jdbc。驱动程序类存在。 但是,当我使用此Class.forName(driver)时,仍然会收到java.lang.ClassNotFoundException。此代码有什么问题? 问题答案: URL可以,但是您仍然尝试

  • 现在,当我运行和时,一切都很好,但是当我尝试运行项目时(在build/libs/中运行构建的jar和在IntelliJ中运行velotest类时也是如此),我会得到以下错误: 线程“main”java.lang.noClassDefFounderror:org/apache/commons/collections/extendedProperties 在org.apache.velocity.ru

  • 我见过很多人对在Eclipse中运行时加载的图像有相同的问题,但在从导出的JAR中运行时没有。我一直有这个问题,直到我找到了正确的解决方案。现在我遇到了一个问题,当从一个新导出的、可运行的JAR运行时,图像被加载,程序工作得非常完美,但在Eclipse中却不行!值得注意的是,我在我的机器上更新了Java! 它在Eclipse中运行得非常好,直到我解决了如何正确导出可运行的JAR,现在却没有!资产文

  • 我使用以下spock依赖项 我写了这个测试 测试成功 现在我想使用Spring在规范中注入用注释,并在运行应用程序时成功注入。测试和组件都放在同一个maven模块中。 我在这里阅读了关于:如何将Spring bean注入spock测试 因此我添加了这个依赖版本 在添加了运行s的依赖后 然后我尝试使用适当的注释 结果仍然是上述例外。 groovy测试在下,这是一个可以在第一次成功运行测试时看到的工作

  • 问题内容: 我被要求构建一个Java系统,该系统在运行时能够加载新代码(扩展)。我的代码运行时如何重新加载jar文件?或如何加载新的jar? 显然,由于持续的正常运行时间很重要,因此,我想增加在此过程中重新加载现有类的功能(如果这样做不会使事情复杂化)。 我应该注意什么?(将其视为两个不同的问题-一个关于在运行时重新加载类,另一个关于添加新类)。 问题答案: 用现有数据重新加载现有类可能会破坏事情