自定义 URLClassLoader,运行时出现 NoClassDefFoundError

发布于 2024-11-03 09:55:10 字数 3082 浏览 5 评论 0原文

我创建了自己的 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

I've created my own URLClassLoader, and set it as the system classloader via java.system.class.loader. It's initialized and everything, but the classes I'm trying to load aren't found. Here's the 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);
    }
}

I've confirmed that the jar exists, and that the path is correct. This is how I call it in my program:

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

This is the exception that I get (line 166 refers to the line at which I try to create a new 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

I even tried explicitly loading the class like so:

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

What might be causing this? Shouldn't it "just work"?


Update: Here's the important code from 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);
        }
    }
}

Update 2: Here's an SSCCE: http://nucleussystems.com/files/myprogram.zip . Call java -Djava.system.class.loader=mp.LibraryLoader -jar myprogram.jar.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

终止放荡 2024-11-10 09:55:10

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

要验证该场景,请打印出文件的完整路径,并查看它对于执行环境是否正确。

编辑


It appears that even if you override the system ClassLoader, the VM will still use the default sun.misc.Launcher$AppClassLoader to load some classes. In my testing this includes the classes that are referenced from the main application. I'm sure there is a reason for this process, however, I am unable to ascertain it at this time. I have come up with a few solutions for you:

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

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 <依赖jar>:launcher.jar mp.AppLauncher

我发现其他答案已更新。既然我已经把上面的评论打出来了,我想我还是应该把它贴出来供大家阅读。

I would have to agree with the comments on this question. Based on the code you have provided, it would appear that you are getting the error due to the JAR files not being where you expect them to be. As mentioned by @Andrew, you are not checking the existence of the file in your addJarToClasspath method. As a result, if the file does not exist, you will receive a ClassNotFound exception as you are seeing. I verified this problem by taking your ClassLoader logic and passing it a valid and an invalid JAR. When a valid JAR/path was provided, the ClassLoader loaded the class as expected. When an invalid JAR/path was specified, I received the error you mentioned. The URLClassLoader does not throw an exception if an URL is specified that does not point to a valid file.

To validate the scenario, print out the path of the full path of your File and see if it is correct for the execution environment.

Edit


It appears that even if you override the system ClassLoader, the VM will still use the default sun.misc.Launcher$AppClassLoader to load some classes. In my testing this includes the classes that are referenced from the main application. I'm sure there is a reason for this process, however, I am unable to ascertain it at this time. I have come up with a few solutions for you:

  • Use a script to detect the environment and set the classpath accordingly. This is perhaps the simplest solution, but one you may or may not want to take based on your particular requirements.
  • Similar to what was mentioned in other answers, specifically load and execute your application using your custom ClassLoader. This does not mean creating a single class that will be loaded and then invoke your application. It means that any class that needs to interact with the dynamically loaded swt libraries and any classes that need to reference your application classes should be loaded from your custom ClassLoader. Any application dependencies, such as log4j, etc, can be referenced by the default application ClassLoader. Here is an example of how this would work:

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): Includes all class which depend on 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);
    }
}

The AppLauncher class would be executed by the VM without the rest of your application being included in the execution Jar.

java -Djava.system.class.loader=test.LibraryLoader -cp <dependency jars>:launcher.jar mp.AppLauncher

I see that there have been updates to the other answers. Since I already had typed up the above comments, I figured that I should still post it for your perusal.

星光不落少年眉 2024-11-10 09:55:10

从几英里之外就可以看到,您没有在 Class.forName 旁边使用自定义类加载器。

自从加载当前类 MyProgram 的类加载器尝试加载以来,就会发生 ClassNoDefFoundError加载 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

It's visible from a (few) mile(s) away you are not using the custom classloader beside Class.forName

The ClassNoDefFoundError occurs since the classloader that has loaded current class MyProgram attempts to load org.eclipse.swt.graphics.Point.

You need to load another class (call it launcher) via Class.forName and then start from there - implement some interface (even runnable will do) and call it.


edit

How to do it, a simplistic scenario.
1. Create another class called mp.loader.Launcher that implements Runnable like that.

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. Place it in another jar called swt-loader.jar.

in MyProgram class use:

loader.addJarToClasspath("swt-loader.jar");
Runnable r = (Runnable) Class.forName("mp.loader.Launcher", true, loader);
r.run();//there you have
乞讨 2024-11-10 09:55:10

由于有问题的行不是 Class.forName 而是 Point 实例的实际初始化,因此我们必须确保尝试加载 Point 类的类是由 Library 类加载器创建的。因此,我根据 此博客对 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!");
    }
}

这应该适合您。

Since the offending line is not the Class.forName but the actual initialization of an instance of Point, we'll have to make sure that the class, that tries to load the Point class, was created by the Library class loader. Therefore, I made some minor changes in the LibraryLoader accordingt to this blog entry

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

In the program itself, we have to extract a new method since all the classes, that are used from within a method, seem to be loaded up-front:

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

That should work for you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文