it-swarm.com.ru

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

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

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

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

Classloader подхватывает только один jar или он смешивает классы произвольно? Так, например, если класс загружен из Version-1.jar, все другие классы, загруженные из того же загрузчика классов, все попадут в тот же самый jar?

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

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

109
jens

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

  • Загрузчики классов в приложении обычно больше, чем один. Загрузчик класса bootstrap делегирует соответствующий. Когда вы создаете новый класс, вызывается более специфический загрузчик классов. Если он не находит ссылку на класс, который вы пытаетесь загрузить, он делегирует его родителю и т.д., Пока не доберетесь до загрузчика классов 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);

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

51
Luca Putzu

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

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

20
Tarlog

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

6
Alex Gitelman

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

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

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

6
Vineet Reynolds

Вы можете использовать URLClassLoader для require для загрузки классов из версии jar 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();
0
Pankaj Kalra