Пользовательский URLClassLoader, NoClassDefFoundError при запуске - PullRequest
3 голосов
/ 27 апреля 2011

Я создал свой 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);
    }
}

Я подтвердил, что банка существует, и что путь правильный.Вот как я называю это в моей программе:

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 ]

2 голосов
/ 27 апреля 2011

Я бы согласился с комментариями по этому вопросу. Исходя из предоставленного вами кода, может показаться, что вы получаете ошибку из-за того, что файлы JAR находятся не там, где вы ожидаете их найти. Как уже упоминалось @Andrew, вы не проверяете наличие файла в вашем методе addJarToClasspath. В результате, если файл не существует, вы получите исключение ClassNotFound, как вы видите. Я проверил эту проблему, взяв логику ClassLoader и передав ей действительный и недействительный JAR. Когда был указан правильный JAR / путь, ClassLoader загрузил класс, как и ожидалось. Когда был указан неверный JAR / путь, я получил указанную вами ошибку. URLClassLoader не генерирует исключение, если указан URL-адрес, который не указывает на допустимый файл.

Чтобы проверить сценарий, распечатайте путь к полному пути вашего файла и посмотрите, подходит ли он для среды выполнения.

Редактировать


Похоже, что даже если вы переопределите системный ClassLoader, виртуальная машина все равно будет использовать значение по умолчанию 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 будет выполняться виртуальной машиной, а остальная часть вашего приложения не будет включена в исполняемый Jar.

java -Djava.system.class.loader = test.LibraryLoader -cp <зависимые банки>: launcher.jar mp.AppLauncher

Я вижу, что были обновления для других ответов. Поскольку я уже напечатал вышеупомянутые комментарии, я решил, что я все еще должен опубликовать его для вашего ознакомления.

1 голос
/ 28 апреля 2011

Это видно из (нескольких) миль (-ей), когда вы не используете пользовательский загрузчик классов, кроме Class.forName

ClassNoDefFoundError происходит, так как загрузчик классов, который загрузил текущий класс MyProgram, пытается загрузить org.eclipse.swt.graphics.Point.

Вам нужно загрузить другой класс (назовите его launcher) через Class.forName, а затем начать с него - реализовать какой-то интерфейс (подойдет даже runnable) и вызвать его.1007 *


edit

Как это сделать, упрощенный сценарий.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.

в классе MyProgram используйте:

loader.addJarToClasspath("swt-loader.jar");
Runnable r = (Runnable) Class.forName("mp.loader.Launcher", true, loader);
r.run();//there you have
1 голос
/ 28 апреля 2011

Так как ошибочная строка не 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!");
    }
}

Это должно сработать для вас.

...