В Java, почему классы исключений должны быть доступны загрузчику классов, прежде чем они будут необходимы? - PullRequest
9 голосов
/ 21 августа 2009

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

В следующем фрагменте показана проблема (DynamicJarLoader - это класс, который фактически загружает JAR; и TestClass, и MyException находятся во внешнем JAR):

public static void main(String[] args) {
    DynamicJarLoader.loadFile("../DynamicTestJar.jar");
    try {
         String foo = new TestClass().testMethod("42");
    } catch(MyException e) { }
}

Когда я пытаюсь запустить его, я получаю это:

Exception in thread "main" java.lang.NoClassDefFoundError: dynamictestjar/MyException
Caused by: java.lang.ClassNotFoundException: dynamictestjar.MyException
        at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
Could not find the main class: dynamicjartestapp.Main. Program will exit.

Если я заменим catch(MyException e) на catch(Exception e), программа будет работать нормально. Это означает, что Java может найти TestClass после того, как JAR уже загружен. Таким образом, похоже, что JVM необходимо определить все классы исключений при запуске программы, а не когда они необходимы (т.е. когда достигнут этот конкретный блок try-catch).

Почему это происходит?

EDIT

Я провел несколько дополнительных тестов, и это действительно довольно странно. Это полный источник MyException:

package dynamictestjar;
public class MyException extends RuntimeException {
    public void test() {
        System.out.println("abc");
    }
}

Этот код работает:

public static void main(String[] args) {
    DynamicJarLoader.loadFile("../DynamicTestJar.jar");
    String foo = new TestClass().testMethod("42");
    new MyException().test(); //prints "abc"
}

Это не:

  public static void main(String[] args) {
    DynamicJarLoader.loadFile("../DynamicTestJar.jar");
    String foo = new TestClass().testMethod("42");
    new MyException().printStackTrace(); // throws NoClassDefFoundError
  }

Я должен отметить, что всякий раз, когда я запускаю свои тесты из NetBeans, все идет по плану. Странность начинается только тогда, когда я принудительно убираю внешний Jar из глаз Java и запускаю тестовое приложение из командной строки.

РЕДАКТИРОВАТЬ # 2

Основываясь на ответах, я написал это, что, я думаю, доказывает, что тот, который я принял, действительно прав:

  public static void main(String[] args) {
    DynamicJarLoader.loadFile("../DynamicTestJar.jar");
    String foo = new TestClass().testMethod("42");
    class tempClass {
        public void test() {
            new MyException().printStackTrace();
        }
    }
    new tempClass().test(); // prints the stack trace, as expected
  }

Ответы [ 4 ]

6 голосов
/ 21 августа 2009

«Похоже, что JVM нужно определить все классы исключений при запуске программы, а не когда они нужны» - нет, я не думаю, что это правильно.

Держу пари, что ваша проблема связана с этими потенциальными ошибками с вашей стороны, ни одна из которых не имеет ничего общего с JVM:

  1. Вам не хватает оператора пакета в верхней части файла MyException.java. Похоже, вы имели в виду «package dynamictestjar», но его там нет.
  2. В вашем файле MyException.java был правильный оператор пакета, но при компиляции вы не получили файл MyException.class в папке с именем dynamictestjar.
  3. Когда вы упаковали свой код в DynamicTestJar.jar (фанат Star Wars, вы), вы не получили правильный путь, потому что вы не заархивировали каталог, содержащий папку «dynamictestjar», поэтому путь к классу загрузчик видит неверно.

Ничто из этого не связано с большими загадками о загрузчике классов. Вам нужно проверить и убедиться, что вы все делаете правильно.

Зачем вам нужно динамически загружать JAR, как это? Что вы делаете, чего нельзя достичь, просто поместив JAR в CLASSPATH и позволив загрузчику классов его поднять?

Надеюсь, это всего лишь пример, а не пример ваших "лучших практик" в Java:

catch(MyException e) { }

Вы должны как минимум распечатать трассировку стека или использовать Log4J для регистрации исключения.

UPDATE:

Я должен отметить, что всякий раз, когда я запускаю свои тесты из NetBeans, все идет по плану. Странность начинается только тогда, когда я принудительно убираю внешний Jar из глаз Java и запускаю тестовое приложение из командной строки.

Это говорит о том, что путь, который вы ввели в путь к JAR-файлу, не выполняется при запуске из командной строки.

JVM не работает в одном направлении с NetBeans, а в другом - без него. Все дело в понимании CLASSPATH, загрузчика классов и проблем с путями. Когда вы разберетесь, это сработает.

3 голосов
/ 21 августа 2009

Является ли MyException классом исключений в JAR, который вы загружаете динамически?

Обратите внимание, что вы статически используете класс MyException, поскольку он буквально присутствует в вашем коде.

Вы помещаете DynamicTestJar.jar в путь к классам во время компиляции, но не во время работы программы? Не помещайте его в classpath во время компиляции, так что компилятор покажет вам, где вы используете JAR статическим способом.

1 голос
/ 21 августа 2009

Вы загружаете JAR DynamicTestJar.jar динамически во время выполнения, но добавляете его в путь к классам при компиляции кода.

Поэтому, когда загрузчик классов по умолчанию пытается загрузить байт-код для main(), он не может найти MyException в пути к классам и выдает исключение. Это происходит до того, как `` DynamicJarLoader.loadFile ("../ DynamicTestJar.jar"); `будет выполнено!

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

0 голосов
/ 21 августа 2009

В Java все классы однозначно идентифицируются загрузчиком классов и FQN класса.

ClassLoaders являются иерархическими, то есть динамически загружаемый класс имеет доступ ко всем классам его родителя, но родитель не имеет прямого доступа к его классам.

Теперь, если ваш дочерний загрузчик классов определяет класс, который расширяет вашего родителя, код вашего родителя может ссылаться на этот класс только через отражение или через родительский класс.

Если вы считаете весь Java-код рефлексивным, это становится проще. IE:

Class.forName("MyException")

Текущий класс не имеет доступа к дочернему загрузчику классов, поэтому он не может этого сделать.

...