简体   繁体   English

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

[英]NoClassDefFoundError when dynamically adding jar files at runtime

UPDATE: I think the issue may be caused by the fact that the TestFramework.jar depends on JUnit and somehow is not finding the junit jar when it (TestFramework) loads. 更新:我认为问题可能是由于TestFramework.jar依赖于JUnit,并且在加载(TestFramework)时无法找到junit jar所致。 Still not sure on a solution, if I'm right I need a way to specify the order in which jars are loaded. 仍然不确定解决方案,如果我是对的,我需要一种方法来指定jar的加载顺序。

First here is some background on what I'm doing: I'm trying to create a tool that will allow users to specify a java source file which is an extension of a JUnit TestCase and contains test data in a method named getTestData. 首先,这里是我正在做的事情的背景知识:我正在尝试创建一个工具,该工具将允许用户指定java源文件,该文件是JUnit TestCase的扩展,并在名为getTestData的方法中包含测试数据。 This tool will then compile the java file, place the resulting class file in the "classes" directory, load the class and access the getTestData method, produce XML files representing the test data. 然后,该工具将编译Java文件,将生成的类文件放置在“ classes”目录中,加载类并访问getTestData方法,生成表示测试数据的XML文件。

Obviously by allowing the user to specify a source file I have to also make sure that all dependencies for that file are included on the classpath when I compile. 显然,通过允许用户指定源文件,我还必须确保在编译时该文件的所有依赖项都包括在类路径中。 So for now I have hardcoded the dependencies for one specific file I'm trying to use for testing purposes. 因此,到目前为止,我已经为要用于测试目的的一个特定文件对依赖项进行了硬编码。 The compilation of the user's file is currently working, but loading the resulting class is causing some issues. 用户文件的编译当前正在工作,但是加载结果类会引起一些问题。 Again it's obvious that to load the class I need to make sure that all files it depends on are on the classpath, which I have. 同样,很明显,要加载该类,我需要确保它依赖的所有文件都在我拥有的类路径上。 Somehow during the loading of the class it still can't find required classes and gives me a NoClassDefFoundError. 在加载类的过程中,它仍然无法找到所需的类,并给了我一个NoClassDefFoundError。

The following is a method I'm using to create a URLClassLoader containing the URL for each of the jar files in the "lib" directory. 以下是我用来创建URLClassLoader的方法,该URLClassLoader包含“ lib”目录中每个jar文件的URL。 Note that the loader is a class variable so it is available to all of my methods but is initialized in this one only. 请注意,加载器是一个类变量,因此它可用于我的所有方法,但仅在此方法中初始化。

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);
    }

}

Here is the code that is actually locating the compiled class file and loading it. 这是实际定位已编译的类文件并加载它的代码。 The first parameter is the directory to search inside of, the second parameter is a list of all the compiled class files that the user wants to generate the XML files for. 第一个参数是要在其中搜索的目录,第二个参数是用户要为其生成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);
            }
        }
    }
}

Something else to note that could be important is that not all the dependencies for the user-selected source file are jars, some are other source files that are also compiled and placed in the "classes" directory. 需要注意的其他重要一点是,并非用户选择的源文件的所有依赖项都是jar,有些是其他源文件,它们也已编译并放置在“ classes”目录中。 The "classes" directory is included in the runtime classpath via project settings (I'm using Netbeans to simplify the GUI creation). “类”目录通过项目设置包含在运行时类路径中(我使用Netbeans来简化GUI创建)。 Since I want the user to be able to dynamically add jars to the "lib" directory I do not specify the jars in the project settings. 因为我希望用户能够将jar动态添加到“ lib”目录中,所以我没有在项目设置中指定jar。

Regarding the output and the problem I'm having here is what I see in my console when I run the code: 关于输出和问题,这是我在运行代码时在控制台中看到的内容:

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 line 299 is the line that reads IvrTest testCase = (IvrTest) Class.forName(fullClassName,true,loader).newInstance(); XmlDataGenerator.java第299行是读取IvrTest testCase = (IvrTest) Class.forName(fullClassName,true,loader).newInstance();

At this point I'm at a loss because I have tried everything I can think of such as: 在这一点上我很茫然,因为我尝试了所有我能想到的东西,例如:

  1. Adding the junit.jar to the runtime classpath (in project settings) to make sure this error goes away, which it does. 将junit.jar添加到运行时类路径(在项目设置中),以确保消除此错误,并且确实如此。 This doesn't help fix the problem though since I want to load jars dynamically. 但是,由于我要动态加载jar,因此这无助于解决问题。
  2. Specifying only the junit.jar instead of trying to load all jars in the directory. 仅指定junit.jar而不是尝试加载目录中的所有jar。 This still results in a failure same as above. 这仍然会导致与上述相同的失败。
  3. Removing the 'classes' directory from the project config and using the same code to bring in those classes. 从项目配置中删除“ classes”目录,并使用相同的代码引入这些类。 This works to bring in the compiled sources but does not fix the jar issue. 这可以引入已编译的源代码,但不能解决jar问题。
  4. Using relative paths in the URLs instead of absolute. 在网址中使用相对路径,而不是绝对路径。 This does not help. 这无济于事。
  5. Adding extra System.out before and after attempting to load the jars - the results are the same as when I print the list of URLs during the creation of the URLClassLoader nothing has changed between when I first created it and after the load is attempted. 在尝试加载jar之前和之后添加额外的System.out-结果与在创建URLClassLoader的过程中打印URL列表时相同,在我第一次创建它与尝试加载之后没有任何变化。

The only thing I can come up with is that somehow the URLClassLoader I'm trying to use is not being used but I have no idea why that would be happening. 我唯一能想到的是,某种程度上我想使用的URLClassLoader没有被使用,但是我不知道为什么会这样。 Any help would be greatly appreciated. 任何帮助将不胜感激。 Thanks for your time. 谢谢你的时间。

I found the problem - since the TestFramework jar was included in the project classpath it would be loaded using whatever default loader is used. 我发现了问题-由于TestFramework jar已包含在项目类路径中,因此可以使用任何默认加载器加载它。 Then when I add more jars later the loader that handled the TestFramework jar does not see them so it thinks the dependencies are missing. 然后,当我以后添加更多的jar时,处理TestFramework jar的加载器看不到它们,因此认为缺少依赖项。 To fix it I created two separate jars, one with interface classes only and the other with all classes so that I could load it together with its dependencies. 为了修复它,我创建了两个单独的jar,一个仅具有接口类,另一个具有所有类,以便可以将其及其依赖项一起加载。

Getting it to always use your classloader can be tricky. 使其始终使用类加载器可能很棘手。 I remember running into this issue and ended up having to create a classloader that is then used to start up the entire app. 我记得遇到了这个问题,最终不得不创建一个类加载器,然后使用该类加载器来启动整个应用程序。

It seemed that a lot of classes wanted to use the standard class loader instead of the one that you loaded them with. 似乎很多类都希望使用标准的类加载器,而不是您使用它们加载的类。

You can also try setting the classloader that you want Java to use using Thread.currentThread().setContextClassLoader() 您也可以尝试使用Thread.currentThread().setContextClassLoader()设置要Java使用的类加载器Thread.currentThread().setContextClassLoader()

One rule about classloaders I remember is that your classloader should always try its parent classloader first before trying to load the class itself. 我记得关于类加载器的一条规则是,在尝试加载类本身之前,您的类加载器应始终首先尝试其父类类加载器。 I see URLClassLoader lets you pass a parent classloader in when constructing. 我看到URLClassLoader允许您在构造时传递父类加载器。 Try passing in the current classloader. 尝试传入当前的类加载器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM