运行时自定义URLClassLoader,NoClassDefFoundError

时间:2011-04-27 19:01:50

标签: java classloader noclassdeffounderror urlclassloader

我已经创建了自己的URLClassLoader,并通过java.system.class.loader将其设置为系统类加载器。它已经初始化了所有内容,但我找不到要加载的类。这是URLClassLoader

public class LibraryLoader extends URLClassLoader
{
    public LibraryLoader(ClassLoader classLoader)
    {
        super(new URL[0], classLoader);
    }
    synchronized public void addJarToClasspath(String jarName) throws MalformedURLException, ClassNotFoundException
    {
        File filePath = new File(jarName);
        URI uriPath = filePath.toURI();
        URL urlPath = uriPath.toURL();

        System.out.println(filePath.exists());
        System.out.println(urlPath.getFile());

        addURL(urlPath);
    }
}

我已经确认jar存在,并且路径是正确的。这就是我在程序中称之为的方式:

LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
loader.addJarToClasspath("swt.jar");

这是我得到的例外(第166行指的是我尝试创建新Point的行:

Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/swt/graphics/Point
        at mp.MyProgram.loadArchitectureLibraries(MyProgram.java:116)
        at mp.MyProgram.main(MyProgram.java:90)
Caused by: java.lang.ClassNotFoundException: org.eclipse.swt.graphics.Point
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 2 more

我甚至试过像这样显式加载这个类:

Class.forName("org.eclipse.swt.graphics.Point", false, loader);

可能导致这种情况的原因是什么?它不应该“只是工作”吗?


更新:以下是MyProgram

中的重要代码
public class MyProgram
{
    // ...

    public static void main(String[] args)
    {
        loadArchitectureLibraries();

        // ...
    }

    public static void loadArchitectureLibraries()
    {
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        String architecture = System.getProperty("os.arch");
        try {
            if (architecture.contains("64")) {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
            } else {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
            }

            Class.forName("org.eclipse.swt.graphics.Point", false, loader);
            org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);

        } catch (Exception exception) {

            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
    }
}

更新2:以下是SSCCE:http://nucleussystems.com/files/myprogram.zip。致电java -Djava.system.class.loader=mp.LibraryLoader -jar myprogram.jar

3 个答案:

答案 0 :(得分:2)

我必须同意对这个问题的评论。根据您提供的代码,由于JAR文件不在您期望的位置,您似乎会收到错误。正如@Andrew所提到的,您没有在addJarToClasspath方法中检查文件是否存在。因此,如果该文件不存在,您将看到一个ClassNotFound异常。我通过使用ClassLoader逻辑并将其传递给有效且无效的JAR来验证此问题。当提供有效的JAR /路径时,ClassLoader按预期加载了类。当指定了无效的JAR /路径时,我收到了您提到的错误。如果指定的URL未指向有效文件,则URLClassLoader不会引发异常。

要验证方案,请打印文件完整路径的路径,并查看它是否适用于执行环境。

修改

<小时/> 看来,即使您覆盖系统ClassLoader,VM仍将使用默认的sun.misc.Launcher$AppClassLoader来加载某些类。在我的测试中,这包括从主应用程序引用的类。我确信这个过程是有原因的,但是,我现在无法确定它。我已经为您提出了一些解决方案:

  • 使用脚本检测环境并相应地设置类路径。这可能是最简单的解决方案,但您可能会或可能不想根据您的特定要求采取这种解决方案。
  • 与其他答案中提到的类似,特别是使用自定义ClassLoader加载和执行您的应用程序。这并不意味着创建一个将被加载然后调用您的应用程序的类。这意味着任何需要与动态加载的swt库交互的类以及需要引用应用程序类的任何类都应该从自定义的ClassLoader加载。默认应用程序ClassLoader可以引用任何应用程序依赖项,例如log4j等。以下是一个如何工作的示例:

JAR 1(launcher.jar):

public class AppLauncher {
    public static void main(String… args) throws Exception {
        ClassLoader loader = initClassLoader();
        Class<?> mpClass = loader.loadClass("mp.MyProgram");

        // using Runnable as an example of how it can be done
        Runnable mpClass = (Runnable) mpClass.newInstance();
    }
    public static ClassLoader initClassLoader() {
        // assuming still configured as system classloader, could also be initialized directly
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        // add the main application jar to the classpath.  
        // You may want to dynamically determine this value (lib folder) or pass it in as a parameter
        loader.addJarToClasspath("myapp.jar");

        String architecture = System.getProperty("os.arch");
        try {
            if (architecture.contains("64")) {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
            } else {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
            }

            Class.forName("org.eclipse.swt.graphics.Point", false, loader);
            org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);

        } catch (Exception exception) {

            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
        return loader;
    }

JAR 2(myapp.jar):包含依赖于swt的所有类

public class MyProgram implements Runnable {
    //…
    public void run() {
    // perform application execution

           // this logic should now work
           org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0,0);
    }
}

AppLauncher类将由VM执行,而其余的应用程序不会包含在执行Jar中。

  

java -Djava.system.class.loader = test.LibraryLoader -cp&lt; dependency jars&gt;:launcher.jar mp.AppLauncher

我看到其他答案都有更新。由于我已经输入了上述评论,我认为我仍然应该发布它以供您阅读。

答案 1 :(得分:1)

由于违规行不是Class.forName而是Point实例的实际初始化,我们必须确保试图加载Point类的类是由Library类加载器创建的。因此,我根据this blog entry

对LibraryLoader进行了一些小的更改
public class LibraryLoader extends URLClassLoader {

    public LibraryLoader(ClassLoader classLoader) {
        super(new URL[0], classLoader);
    }

    synchronized public void addJarToClasspath(String jarName)
            throws MalformedURLException, ClassNotFoundException {
        File filePath = new File(jarName);
        URI uriPath = filePath.toURI();
        URL urlPath = uriPath.toURL();

        System.out.println(filePath.exists());
        System.out.println(urlPath.getFile());

        addURL(urlPath);
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if ("mp.MyProgram".equals(name)) {
            return getClass(name);
        }
        return super.loadClass(name, resolve);
    }

    private Class<?> getClass(String name) throws ClassNotFoundException {
        String file = name.replace('.', File.separatorChar) + ".class";
        byte[] b = null;
        try {
            b = loadClassData(file);
            Class<?> c = defineClass(name, b, 0, b.length);
            resolveClass(c);
            return c;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private byte[] loadClassData(String name) throws IOException {
        InputStream stream = getClass().getClassLoader().getResourceAsStream(
                name);
        int size = stream.available();
        byte buff[] = new byte[size];
        DataInputStream in = new DataInputStream(stream);
        in.readFully(buff);
        in.close();
        return buff;
    }
}

在程序本身中,我们必须提取一个新方法,因为在方法中使用的所有类似乎都是预先加载的:

public class MyProgram {
    public static void main(String[] args) {
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        String architecture = System.getProperty("os.arch");
        try {
            loader.addJarToClasspath("swt.jar");
            otherMethod();

        } catch (Throwable exception) {
            // println instead of logger because logging is useless at this level
            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
    }

    protected static void otherMethod() {
        org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
        System.out.println("Works!");
    }
}

那应该适合你。

答案 2 :(得分:1)

从(几英里)远的地方可以看到您没有使用Class.forName旁边的自定义类加载器

发生ClassNoDefFoundError,因为已加载当前类MyProgram的类加载器尝试加载org.eclipse.swt.graphics.Point。

你需要通过Class.forName加载另一个类(称之为启动程序)然后从那里开始 - 实现一些接口(甚至runnable会这样做)并调用它。


修改

怎么做,一个简单的场景。
1.创建另一个名为mp.loader.Launcher的类,它实现Runnable。

public class Launcher implements Runnable{
public void run(){
  org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
  //whatever, start from here.
}
}

2。将它放在另一个名为swt-loader.jar的jar中。

在MyProgram类中使用:

loader.addJarToClasspath("swt-loader.jar");
Runnable r = (Runnable) Class.forName("mp.loader.Launcher", true, loader);
r.run();//there you have