在运行时动态添加jar文件时的NoClassDefFoundError

时间:2012-04-19 15:27:29

标签: java jar classloader urlclassloader

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

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

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

以下是我用来创建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,有些是其他源文件,它们也被编译并放在“classes”目录中。 “classes”目录通过项目设置包含在运行时类路径中(我使用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. 从项目配置中删除“classes”目录,并使用相同的代码引入这些类。这可以引入已编译的源代码,但不能解决jar问题。
  4. 使用网址中的相对路径而不是绝对路径。这没有用。
  5. 在尝试加载jar之前和之后添加额外的System.out - 结果与在创建URLClassLoader期间打印URL列表时的结果相同,在我第一次创建它和加载之后没有任何变化尝试。
  6. 我唯一能想到的是,我试图使用的URLClassLoader不会被使用,但我不知道为什么会发生这种情况。任何帮助将不胜感激。谢谢你的时间。

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

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

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

您还可以尝试使用Thread.currentThread().setContextClassLoader()

设置希望Java使用的类加载器

我记得关于类加载器的一条规则是你的类加载器应该在尝试加载类本身之前首先尝试它的父类加载器。我看到URLClassLoader允许你在构造时传递父类加载器。尝试传入当前的类加载器。