Java, Classpath, Classloading => Несколько версий одного и того же JAR / проекта - PullRequest
110 голосов
/ 24 мая 2011

Я знаю, что это может быть глупый вопрос для опытных программистов. Но у меня есть библиотека (клиент 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 подхватывает только одну банку или произвольно смешивает классы? Так, например, если класс загружен из Version-1.jar, все другие классы, загруженные из того же загрузчика классов, все попадут в тот же самый jar?

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

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

Ответы [ 5 ]

51 голосов
/ 24 мая 2011

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

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

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

20 голосов
/ 24 мая 2011

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

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

6 голосов
/ 24 мая 2011

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

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

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

6 голосов
/ 24 мая 2011

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

0 голосов
/ 24 апреля 2019

Вы можете использовать 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();
...