Черт возьми: как использовать загрузчик классов для замены одной версии библиотеки jar на другую во время выполнения

Я все еще относительно новичок в 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 имеют взаимную зависимость от Library3, но Library1 требуется именно версия 1, а Library2 требуется именно версия 2. В этом примере обе эти библиотеки просто распечатывают версию Library3, которую они видят:

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 (требуется для библиотеки 2):

public class Library3 {
  public void printVersion() {
    System.out.println("This is version 2.");
  }
}

Когда я запускаю приложение, путь к классам содержит библиотеку 1 (lib1 .jar), Library2 (lib2.jar) и версии 1 библиотеки 3 (lib3-v1 / lib3.jar). Это отлично работает для Library1, но не сработает для Library2.

Что мне нужно сделать, так это заменить версию Library3, которая появляется в пути к классам, до создания экземпляра 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's 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());
    }
  }
}

Когда я запускаю this, lib1.foo () вызывает «Это версия 1.» для печати. Так как это версия Library3, которая находится в пути к классам при запуске приложения, это ожидается.

Однако я ожидал, что lib2.bar () напечатает «Это версия 2», отражая это. новая версия Library3 была загружена, но она по-прежнему печатает «Это версия 1.»

Почему использование нового загрузчика классов с загруженной правильной версией jar по-прежнему приводит к использованию старой версии jar? Я делаю что-то неправильно? Или я не понимаю концепцию загрузчиков классов?Как мне правильно переключить jar-версии Library3 во время выполнения?

Буду признателен за любую помощь по этой проблеме.

31
задан Sergey K 22 December 2015 в 15:17
поделиться