【问题标题】:NoClassDefFoundError when dynamically adding jar files at runtime在运行时动态添加 jar 文件时出现 NoClassDefFoundError
【发布时间】:2012-05-01 04:02:05
【问题描述】:

更新:我认为问题可能是由于 TestFramework.jar 依赖于 JUnit 并且在 (TestFramework) 加载时以某种方式找不到 junit jar。仍然不确定解决方案,如果我是对的,我需要一种方法来指定 jar 的加载顺序。

首先这里是我正在做的一些背景:我正在尝试创建一个工具,允许用户指定一个 java 源文件,该文件是 JUnit TestCase 的扩展,并在名为 getTestData 的方法中包含测试数据.该工具将编译 java 文件,将生成的类文件放在“classes”目录中,加载类并访问 getTestData 方法,生成代表测试数据的 XML 文件。

显然,通过允许用户指定源文件,我还必须确保在编译时该文件的所有依赖项都包含在类路径中。所以现在我已经硬编码了一个我试图用于测试目的的特定文件的依赖关系。当前正在编译用户文件,但加载生成的类会导致一些问题。同样很明显,要加载类,我需要确保它所依赖的所有文件都在我拥有的类路径上。不知何故,在加载类的过程中,它仍然找不到所需的类,并给了我一个 NoClassDefFoundError。

以下是我用来创建 URLClassLoader 的方法,该 URLClassLoader 包含“lib”目录中每个 jar 文件的 URL。请注意,加载器是一个类变量,因此它可用于我的所有方法,但仅在此方法中初始化。

private void loadLibJars()
{
    try {
        File jarDir = new File("lib");
        ArrayList<URL> jarFiles = new ArrayList<URL>();

        for(File file: jarDir.listFiles())
        {
            if(file.isFile() && file.getName().endsWith(".jar"))
            {
                jarFiles.add(file.toURI().toURL());
            }
        }

        for(URL url:jarFiles)
            System.out.println(url);

        URL[] urlArray = new URL[jarFiles.size()];
        for(int i=0; i<jarFiles.size(); i++)
            urlArray[i] = jarFiles.get(i);

        loader = new URLClassLoader(urlArray);
    } 
    catch (MalformedURLException ex) {
        Logger.getLogger(XmlDataGenerator.class.getName()).log(Level.SEVERE, null, ex);
    }

}

这是实际定位已编译的类文件并加载它的代码。第一个参数是要在其中搜索的目录,第二个参数是用户想要为其生成 XML 文件的所有已编译类文件的列表。

private void findAndLoadClasses(File classesDir, ArrayList<String>classFileNames)
{
    for(File classFile: classesDir.listFiles())
    {
        if(classFile.isDirectory())
        {
            System.out.println(classFile+" is a directory, searching inside of it now");
            findAndLoadClasses(classFile,classFileNames);
        }
        else if(classFile.isFile() && classFileNames.contains(classFile.getName()))
        {
            try
            {
                String fullClassName = classFile.getPath();
                fullClassName = fullClassName.substring(fullClassName.indexOf("\\")+1,fullClassName.lastIndexOf("."));
                fullClassName = fullClassName.replaceAll("\\\\", ".");

                System.out.println("Full class name: "+fullClassName);

                IvrTest testCase =  (IvrTest) Class.forName(fullClassName,true,loader).newInstance();

                System.out.println("Test data from "+classFile.getName()+": "+testCase.getTestData(...));
            }
            catch(Exception ex)
            {
                Logger.getLogger(XmlDataGenerator.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

还有一点需要注意,可能很重要的是,并非用户选择的源文件的所有依赖项都是 jar,有些是其他源文件,它们也被编译并放置在“类”目录中。 “类”目录通过项目设置包含在运行时类路径中(我使用 Netbeans 来简化 GUI 创建)。由于我希望用户能够将 jar 动态添加到“lib”目录中,因此我没有在项目设置中指定 jar。

关于输出和我在这里遇到的问题是我在运行代码时在控制台中看到的:

List of class files to search for: [MyTest.class]
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/client.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/delegate.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/model.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/common.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/framework.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/TestFramework.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/junit.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/log4j-1.2.15.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/org.springframework.beans-3.0.1.RELEASE-A.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/org.springframework.context-3.0.1.RELEASE-A.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/org.springframework.core-3.0.1.RELEASE-A.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/org.springframework.web-3.0.1.RELEASE-A.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/org.springframework.web.servlet-3.0.1.RELEASE-A.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/reports.jar
file:/C:/Documents%20and%20Settings/username/My%20Documents/NetBeansProjects/XmlDataGenerator/lib/servlet.jar
classes\my is a directory, searching inside of it now
classes\my\package is a directory, searching inside of it now
classes\my\package\name is a directory, searching inside of it now
Full class name: my.package.name.MyTest
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: junit/framework/TestCase
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:296)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:247)
        at xmldatagenerator.main.XmlDataGenerator.findAndLoadClasses(XmlDataGenerator.java:299)
        at xmldatagenerator.main.XmlDataGenerator.findAndLoadClasses(XmlDataGenerator.java:285)
        at xmldatagenerator.main.XmlDataGenerator.findAndLoadClasses(XmlDataGenerator.java:285)
        at xmldatagenerator.main.XmlDataGenerator.findAndLoadClasses(XmlDataGenerator.java:285)
        at xmldatagenerator.main.XmlDataGenerator.findAndLoadClasses(XmlDataGenerator.java:285)
        at xmldatagenerator.main.XmlDataGenerator.findAndLoadClasses(XmlDataGenerator.java:285)
        at xmldatagenerator.main.XmlDataGenerator.generateXmlBtnActionPerformed(XmlDataGenerator.java:242)
        at xmldatagenerator.main.XmlDataGenerator.access$300(XmlDataGenerator.java:33)
        at xmldatagenerator.main.XmlDataGenerator$4.actionPerformed(XmlDataGenerator.java:104)
        at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
        at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
        at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
        at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
        at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
        at java.awt.Component.processMouseEvent(Component.java:6267)
        at javax.swing.JComponent.processMouseEvent(JComponent.java:3267)
        at java.awt.Component.processEvent(Component.java:6032)
        at java.awt.Container.processEvent(Container.java:2041)
        at java.awt.Component.dispatchEventImpl(Component.java:4630)
        at java.awt.Container.dispatchEventImpl(Container.java:2099)
        at java.awt.Component.dispatchEvent(Component.java:4460)
        at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4577)
        at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238)
        at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168)
        at java.awt.Container.dispatchEventImpl(Container.java:2085)
        at java.awt.Window.dispatchEventImpl(Window.java:2478)
        at java.awt.Component.dispatchEvent(Component.java:4460)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Caused by: java.lang.ClassNotFoundException: junit.framework.TestCase
        at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
        ... 73 more

XmlDataGenerator.java 第 299 行是读取 IvrTest testCase = (IvrTest) Class.forName(fullClassName,true,loader).newInstance(); 的行

此时我不知所措,因为我已经尝试了所有我能想到的方法,例如:

  1. 将 junit.jar 添加到运行时类路径(在项目设置中)以确保此错误消失,它确实如此。这无助于解决问题,因为我想动态加载 jar。
  2. 仅指定 junit.jar 而不是尝试加载目录中的所有 jar。这仍然会导致与上述相同的失败。
  3. 从项目配置中删除“类”目录并使用相同的代码引入这些类。这可以引入已编译的源代码,但不能解决 jar 问题。
  4. 在 URL 中使用相对路径而不是绝对路径。这没有帮助。
  5. 在尝试加载 jar 之前和之后添加额外的 System.out - 结果与我在创建 URLClassLoader 期间打印 URL 列表时相同,从我第一次创建它到加载之后没有任何变化尝试过。

我唯一能想到的是,不知何故我尝试使用的 URLClassLoader 没有被使用,但我不知道为什么会发生这种情况。任何帮助将不胜感激。感谢您的宝贵时间。

【问题讨论】:

  • 为什么 1) 不能帮助解决问题?您似乎正在加载一个扩展 TestCase 的类,该类在 junit.jar 中可用,但不在您的类路径中。
  • 它不能解决我的问题,因为我想动态加载 jars。如果我必须在项目设置中指定 jar,那么如果有人使用它需要一个新的 jar,我必须重新构建这个工具。
  • 我同意创建启动器可以工作,但我很想知道为什么我当前的代码不起作用。我使用一些更简单的类编写了一小段代码,我可以让它在运行时加载 jar 而不会出现这个问题,所以我想了解这里发生了什么。如果我无法调试此问题,我会考虑使用启动器作为最后的手段。
  • 我认为您不必担心订单。类加载的工作方式是它总是查看它是否有它需要的类,如果没有它就开始寻找它。

标签: java jar classloader urlclassloader


【解决方案1】:

我发现了问题 - 因为 TestFramework jar 包含在项目类路径中,它将使用使用的任何默认加载器加载。然后,当我稍后添加更多 jar 时,处理 TestFramework jar 的加载程序看不到它们,因此它认为缺少依赖项。为了解决这个问题,我创建了两个单独的 jar,一个仅包含接口类,另一个包含所有类,以便我可以将其与其依赖项一起加载。

【讨论】:

    【解决方案2】:

    让它始终使用您的类加载器可能很棘手。我记得遇到过这个问题,最后不得不创建一个类加载器,然后用来启动整个应用程序。

    似乎很多类都想使用标准类加载器,而不是你加载它们的那个。

    您也可以尝试使用 Thread.currentThread().setContextClassLoader() 设置您希望 Java 使用的类加载器

    我记得关于类加载器的一条规则是,在尝试加载类本身之前,您的类加载器应始终先尝试其父类加载器。我看到 URLClassLoader 允许您在构建时传递父类加载器。尝试传入当前的类加载器。

    【讨论】:

    • 感谢您的建议,但是,我确实尝试过,但没有成功。我能够使用我发布的解决方案解决此问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-28
    • 1970-01-01
    • 2021-09-28
    • 2013-04-26
    • 1970-01-01
    相关资源
    最近更新 更多