3

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

4

3 回答 3

2

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

要验证场景,请打印出您的文件的完整路径的路径,并查看它是否适合执行环境。

编辑


看来,即使你覆盖了系统的ClassLoader,VM仍然会使用默认值sun.misc.Launcher$AppClassLoader来加载一些类。在我的测试中,这包括从主应用程序引用的类。我确信这个过程是有原因的,但是,我目前无法确定。我为您提出了一些解决方案:

  • 使用脚本来检测环境并相应地设置类路径。这可能是最简单的解决方案,但根据您的特定要求,您可能想也可能不想采用。
  • 与其他答案中提到的类似,特别是使用您的自定义 ClassLoader 加载和执行您的应用程序。这并不意味着创建一个将被加载然后调用您的应用程序的类。这意味着任何需要与动态加载的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 <依赖 jars>:launcher.jar mp.AppLauncher

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

于 2011-04-27T19:35:50.073 回答
1

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

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

您需要通过 Class.forName 加载另一个类(称为启动器),然后从那里开始 - 实现一些接口(甚至可以运行)并调用它。


编辑

如何做到这一点,一个简单的场景。
1. 创建另一个名为mp.loader.LauncherRunnable 的类。

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
于 2011-04-27T21:14:37.013 回答
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!");
    }
}

那应该对你有用。

于 2011-04-27T20:17:38.740 回答