Java, Classpath, Classloading => Несколько версий одного и того же jar/project

Я знаю, что это может быть глупый вопрос для опытных кодеров. Но у меня есть библиотека (клиент-клиент), которую требуют некоторые другие фреймворки/баночки, используемые в моем проекте. Но все они требуют разных основных версий, таких как:

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

Является ли классный загрузчик достаточно интеллектуальным, чтобы каким-то образом их отделить? Скорее всего, нет? Как это делает Classloader, если класс одинаковый во всех трех баночках. Какой из них загружен и почему?

Разделяет ли Classloader только одну банку или смешивает классы произвольно? Так, например, если класс загружен из Версии 1.jar, все остальные классы, загруженные из одного загрузчика классов, будут поступать в одну банку?

Как вы справляетесь с этой проблемой?

Есть ли какой-то трюк, чтобы каким-то образом "включить" банки в "required.jar", чтобы их можно было обозначить как "один блок/пакет" с помощью Classloader или каким-то образом связанным?

Ответ 1

Проблемы, связанные с загрузкой Classloader, - довольно сложный вопрос. В любом случае вам следует иметь в виду некоторые факты:

  • Погрузчики классов в приложении обычно более одного. Загрузочный загрузчик класса bootstrap делегирует соответствующее. Когда вы создаете экземпляр нового класса, вызывается более конкретный загрузчик классов. Если он не находит ссылку на класс, который вы пытаетесь загрузить, он делегирует его родительскому устройству и так далее, пока не дойдете до загрузчика класса загрузки. Если ни один из них не найдет ссылку на класс, который вы пытаетесь загрузить, вы получите исключение ClassNotFoundException.

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

  • В соответствии с спецификацией java-языка для двоичного имени класса нет ограничения уникальности, но, насколько я вижу, он должен быть уникальным для каждого загрузчика классов.

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

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

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

Ответ 2

Каждая загрузка класса выбирает ровно один класс. Обычно первый найден.

OSGi направлен на решение проблемы нескольких версий одной и той же банки. Equinox и Apache Felix являются обычным открытым исходным кодом реализации для OSGi.

Ответ 3

Classloader будет загружать классы из jar, которые были сначала в пути к классам. Обычно несовместимые версии библиотеки будут иметь разницу в пакетах, Но в маловероятном случае они действительно несовместимы и не могут быть заменены одной - попыткой jarjar.

Ответ 4

Класс Loadload загружает класс по требованию. Это означает, что класс, требуемый сначала вашим приложением и связанными с ним библиотеками, будет загружен перед другими классами; запрос на загрузку зависимых классов обычно выдается во время процесса загрузки и компоновки зависимого класса.

Вероятно, вы столкнетесь с LinkageError, указав, что дублированные определения классов встречаются для загрузчиков классов, как правило, не пытаются определить, какой класс должен быть загружен первым (если есть два или более классов с одним и тем же именем, присутствующим в пути класса погрузчик). Иногда загрузчик классов загружает первый класс, встречающийся в пути к классам, и игнорирует повторяющиеся классы, но это зависит от реализации загрузчика.

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

Ответ 5

Вы можете использовать URLClassLoader для загрузки классов из версии diff-2:

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();