#java #classloader #noclassdeffounderror #urlclassloader
#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
.
Комментарии:
1. Не могли бы вы, пожалуйста, также опубликовать код класса MyProgram?
2. Просматривается ли ваш загрузчик классов в jre / ext? Есть ли в вашем jre / ext что-нибудь важное? Какое исключение вы получите, если выполните Class.forName(«org.eclipse.swt.graphics. Точка», истина , загрузчик);
3. @Jonah: «Я подтвердил, что jar существует, и что путь правильный». Нигде в этих фрагментах кода он не вызывает
File.exists()
orSystem.out.println(File.getCanonicalPath())
или что-то подобное, поэтому нам просто придется поверить вам на слово. (считает) На самом деле, нет, нам не нужно делать ничего подобного. Почему вы не публикуете SSCCE ?4. @Andrew: У меня действительно был этот код там, я просто удалил его 🙂 Я вставлю его обратно.
5. Получаете ли вы ошибку NoClassDefFoundError в строке org.eclipse.swt.graphics. Точка pt = новая организация.eclipse.swt.graphics. Точка (0, 0); ? Или это вызвано вызовом Class.forName(..)? Не могли бы вы, пожалуйста, убедиться, что MyProgram.class.getClassLoader() возвращает ваш LibraryClassLoader?
Ответ №1:
Я должен был бы согласиться с комментариями по этому вопросу. На основе предоставленного вами кода может показаться, что вы получаете ошибку из-за того, что файлы JAR находятся не там, где вы ожидаете, что они будут. Как упоминалось @Andrew, вы не проверяете существование файла в вашем методе addJarToClasspath. В результате, если файл не существует, вы получите исключение ClassNotFound, которое вы видите. Я проверил эту проблему, взяв вашу логику ClassLoader и передав ей допустимый и недопустимый JAR. Когда был предоставлен допустимый JAR / путь, ClassLoader загрузил класс, как и ожидалось. Когда был указан недопустимый JAR / path, я получил ошибку, о которой вы упомянули. URLClassLoader не выдает исключение, если указан URL, который не указывает на допустимый файл.
Для проверки сценария распечатайте полный путь к вашему файлу и посмотрите, соответствует ли он среде выполнения.
Редактировать
Похоже, что даже если вы переопределите системный ClassLoader, виртуальная машина все равно будет использовать значение по умолчанию sun.misc.Launcher$AppClassLoader
для загрузки некоторых классов. В моем тестировании это включает классы, на которые ссылаются из основного приложения. Я уверен, что для этого процесса есть причина, однако на данный момент я не могу ее установить. Я придумал для вас несколько решений:
- Используйте скрипт для определения среды и соответствующего задания пути к классу. Это, пожалуй, самое простое решение, но вы можете захотеть, а можете и не захотеть, исходя из ваших конкретных требований.
- Аналогично тому, что упоминалось в других ответах, в частности, загрузите и запустите ваше приложение, используя ваш пользовательский ClassLoader. Это не означает создания отдельного класса, который будет загружен и затем вызовет ваше приложение. Это означает, что любой класс, которому необходимо взаимодействовать с динамически загружаемыми
swt
библиотеками, и любые классы, которым необходимо ссылаться на классы вашего приложения, должны быть загружены из вашего пользовательского 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
Класс будет выполняться виртуальной машиной без включения остальной части вашего приложения в Jar выполнения.
java -Djava.system.class.loader=тест.LibraryLoader -cp <банки зависимостей>:launcher.jar mp.AppLauncher
Я вижу, что были обновления для других ответов. Поскольку я уже напечатал приведенные выше комментарии, я решил, что мне все равно следует опубликовать их для вашего ознакомления.
Комментарии:
1. да, это правильно. Я проверил
exists()
и путь вручную. Я создаю SSCCE прямо сейчас…2. Вау, спасибо за все исследования. Но @ Sebastian только что заставил это работать для меня, без отдельного jar 🙂
3. Не проблема, всегда здесь, чтобы помочь.
4. Теперь я вижу, что ты говоришь то же самое, что и @bestsss. 1 🙂
Ответ №2:
Это видно с расстояния (нескольких) миль, когда вы не используете пользовательский classloader рядом Class.forName
Ошибка ClassNoDefFoundError возникает, поскольку загрузчик классов, который загрузил текущий класс MyProgram
, пытается загрузить org.eclipse.swt.graphics.Точка.
Вам нужно загрузить другой класс (назовите его launcher) через 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. Поместите его в другой jar с именем swt-loader.jar .
в классе моей программы используется:
loader.addJarToClasspath("swt-loader.jar");
Runnable r = (Runnable) Class.forName("mp.loader.Launcher", true, loader);
r.run();//there you have
Комментарии:
1. Поскольку загрузчик libary является загрузчиком системного класса, его следует использовать и для загрузки класса MyProgram, не так ли?
2. @Sebastian Поскольку загрузчик библиотеки является загрузчиком системного класса… это не так. Системный загрузчик — это тот, который загрузил класс с помощью метода main (проще говоря)
3. @Джона, я думаю, ты упускаешь из виду тот аспект, как работает механизм загрузки классов в java, загрузчик классов, у которого есть загрузчик текущего выполняемого класса, будет использоваться для загрузки новых классов. Я отредактирую код, чтобы показать, как это сделать правильно. Но общая идея заключается в том, что вы должны поместить еще один jar для начальной загрузки и использовать
swt.jar
один. По сути, вы хотите получить доступ к классам вswt.jar
ТОЛЬКО из того же загрузчика. В основном из-за того, что этого не удается сделать, возникает ошибка NoClassDefFoundError (по праву).4. Аргумент командной строки -Djava.system.class.loader=mp.LibraryLoader использовался для установки загрузчика системного класса в загрузчик библиотеки.
5. @Джона, если у тебя есть опыт работы с веб-приложениями, они являются хорошим примером. [Ну, почти, поскольку загрузка веб-приложений нарушает некоторые стандарты, но это не важно.] Но практически классы за пределами веб-приложения могут не обращаться ни к какому классу, находящемуся в нем. Действительно, вы можете экспортировать общие интерфейсы для выполнения какой-либо задачи, и в этом заключается идея использования интерфейсов.
Ответ №3:
Поскольку нарушающая строка — это не Class.forName, а фактическая инициализация экземпляра Point, нам нужно убедиться, что класс, который пытается загрузить класс Point, был создан загрузчиком библиотечных классов. Поэтому я внес некоторые незначительные изменения в 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!");
}
}
Это должно сработать для вас.
Комментарии:
1. @Sebastian: Я пробовал, но получаю ту же ошибку. Странно, когда я пытаюсь получить доступ к загрузчику классов изнутри потока, он говорит, что это не мой LibraryLoader. Итак, я получил это, установив для переменной loader значение
final
.2. это не сработает, CCL используется совершенно по-другому. Вам нужно, чтобы выполняемый impl загружался с помощью libraryLoader
3. @bestsss: что именно вы подразумеваете под «загрузкой выполняемого файла с помощью libraryLoader»?
4. К сожалению, LibraryLoader сначала делегирует исходный системный classloader, который загрузит runnable. Класс runnable будет связан с этим загрузчиком класса ‘system’ и, следовательно, не увидит swt. Точка.
5. @Sebastian: то есть вы хотите сказать, что этот конкретный метод не будет работать?