Я все еще относительно новичок в Java, поэтому, пожалуйста, несите меня.
Моя проблема в том, что мое приложение Java зависит от двух библиотек. Позвольте называть их библиотекой 1 и библиотекой 2. Обе библиотеки имеют общую зависимость от библиотеки 3. Однако:
- Для библиотеки 1 требуется точно версия 1 библиотеки 3.
- Библиотека 2 требует ровно версии 2 библиотеки 3.
Это как раз определение JAR hell (или хотя бы один его вариант). Как указано в ссылке, я не могу загрузить обе версии третьей библиотеки в один и тот же загрузчик классов. Таким образом, я пытался выяснить, могу ли я создать новый загрузчик классов в приложении для решения этой проблемы. Я смотрел URLClassLoader, но я не смог понять это.
Вот пример структуры приложения, которая демонстрирует проблему. Основной класс (Main.java) приложения пытается создать экземпляр как Library1, так и Library2 и запустить некоторый метод, определенный в этих библиотеках:
Main.java(оригинальная версия перед любой попыткой решения):
public class Main {
public static void main(String[] args) {
Library1 lib1 = new Library1();
lib1.foo();
Library2 lib2 = new Library2();
lib2.bar();
}
}
Library1 и Library2 имеют общую зависимость от библиотеки3, но для библиотеки 1 требуется точно версия 1, а для библиотеки 2 требуется точно версия 2. В этом примере обе эти библиотеки просто распечатывают версию библиотеки3, которую они видят:
Library1.java:
public class Library1 {
public void foo() {
Library3 lib3 = new Library3();
lib3.printVersion(); // Should print "This is version 1."
}
}
Library2.java:
public class Library2 {
public void foo() {
Library3 lib3 = new Library3();
lib3.printVersion(); // Should print "This is version 2." if the correct version of Library3 is loaded.
}
}
И тогда, конечно, есть несколько версий Library3. Все, что они делают, это распечатать номера своих версий:
Версия 1 библиотеки3 (требуется для библиотеки 1):
public class Library3 {
public void printVersion() {
System.out.println("This is version 1.");
}
}
Версия 2 библиотеки3 (требуется Library2):
public class Library3 {
public void printVersion() {
System.out.println("This is version 2.");
}
}
Когда я запускаю приложение, путь к классу содержит Library1 (lib1.jar), Library2 (lib2.jar) и версию 1 библиотеки 3 (lib3-v1/lib3.jar). Это отлично работает для Library1, но не будет работать для Library2.
Что-то мне нужно сделать, это заменить версию библиотеки3, которая появляется в пути к классам, прежде чем создавать экземпляр Library2. У меня создалось впечатление, что для этого можно использовать URLClassLoader, вот что я пробовал:
Main.java(новая версия, включая мою попытку решения):
import java.net.*;
import java.io.*;
public class Main {
public static void main(String[] args)
throws MalformedURLException, ClassNotFoundException,
IllegalAccessException, InstantiationException,
FileNotFoundException
{
Library1 lib1 = new Library1();
lib1.foo(); // This causes "This is version 1." to print.
// Original code:
// Library2 lib2 = new Library2();
// lib2.bar();
// However, we need to replace Library 3 version 1, which is
// on the classpath, with Library 3 version 2 before attempting
// to instantiate Library2.
// Create a new classloader that has the version 2 jar
// of Library 3 in its list of jars.
URL lib2_url = new URL("file:lib2/lib2.jar"); verifyValidPath(lib2_url);
URL lib3_v2_url = new URL("file:lib3-v2/lib3.jar"); verifyValidPath(lib3_v2_url);
URL[] urls = new URL[] {lib2_url, lib3_v2_url};
URLClassLoader c = new URLClassLoader(urls);
// Try to instantiate Library2 with the new classloader
Class<?> cls = Class.forName("Library2", true, c);
Library2 lib2 = (Library2) cls.newInstance();
// If it worked, this should print "This is version 2."
// However, it still prints that it version 1. Why?
lib2.bar();
}
public static void verifyValidPath(URL url) throws FileNotFoundException {
File filePath = new File(url.getFile());
if (!filePath.exists()) {
throw new FileNotFoundException(filePath.getPath());
}
}
}
Когда я запускаю это, lib1.foo()
вызывает "Это версия 1." для печати. Поскольку это версия библиотеки3, которая на пути к классу при запуске приложения, это ожидается.
Однако я ожидал, что lib2.bar()
напечатает "This is version 2.", что отражает загрузку новой версии Library3, но она все еще печатает "Это версия 1".
Почему использование нового загрузчика классов с правильной загруженной версией jar-версии приводит к использованию старой версии jar? Я делаю что-то неправильно? Или я не понимаю концепцию загрузчиков классов? Как я могу корректно переключать jar-версии библиотеки3 во время выполнения?
Буду признателен за любую помощь по этой проблеме.