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

#java #jar #classpath #classloader

#java #jar #classpath #classloader

Вопрос:

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

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

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

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

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

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

Ответ №1:

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

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

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

  • Согласно спецификации языка 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);
  

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

Комментарии:

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

2. Нет — обычно classloader делегирует своему родителю, прежде чем искать сам класс. Смотрите Classloader в классе javadoc.

3. Я думаю, что tomcat делает это описанным здесь способом, но «обычное» делегирование заключается в том, чтобы сначала спросить родительский

4. @deckingraj: после некоторого поиска в Google я нашел это в документах oracle docs: «В проекте делегирования загрузчик классов делегирует загрузку классов своему родительскому, прежде , чем пытаться загрузить сам класс. […] Если загрузчик родительского класса не может загрузить класс, загрузчик классов пытается загрузить сам класс. По сути, загрузчик классов отвечает за загрузку только классов, недоступных родительскому «. Я продолжу расследование. Если это появится в качестве реализации по умолчанию, я соответствующим образом обновлю ответ. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )

5. Просто для ясности, пользовательский classloader может делать все, что ему заблагорассудится . Сначала у него может быть родительская проверка, или он может сначала выполнить это сам, или он может sys.напечатать эмодзи. Если вы записываете загрузчик, вы переопределяете метод, нет никакой гарантии, что произойдет.

Ответ №2:

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

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

Ответ №3:

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

Ответ №4:

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

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

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

Ответ №5:

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

 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();