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

119

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

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

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

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

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

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

Jens
источник

Ответы:

58

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

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

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

Лука Путцу
источник
14
Загрузчик классов начальной загрузки делегирует соответствующие полномочия. Когда вы создаете экземпляр нового класса, вызывается более конкретный загрузчик классов. Если он не находит ссылку на класс, который вы пытаетесь загрузить, он делегирует свой родительский элемент. Пожалуйста, не стесняйтесь, но это зависит от политики загрузчика классов, которая по умолчанию является Parent First. Другими словами, дочерний класс сначала попросит своего родителя загрузить класс и загрузится, только если его не удалось загрузить всей иерархии, нет ??
deckingraj
5
Нет - обычно загрузчик классов делегирует полномочия своему родителю, прежде чем искать сам класс. См. Класс javadoc для Classloader.
Джо Кирни
1
Я думаю, что Tomcat делает это так, как описано здесь, но «обычное» делегирование - это сначала спросить у родителя
rogerdpack
@deckingraj: после некоторого поиска в Google я нашел это в документации оракула: «В проекте делегирования загрузчик класса делегирует загрузку класса своему родителю, прежде чем пытаться загрузить сам класс. [...] Если загрузчик родительского класса не может загрузить класс, загрузчик классов пытается загрузить сам класс. Фактически, загрузчик классов отвечает за загрузку только классов, недоступных для родительского элемента ". Я буду исследовать дальше. Если это станет реализацией по умолчанию, я соответствующим образом обновлю ответ. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Лука Путцу,
20

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

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

Tarlog
источник
6

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

Алексей Гительман
источник
6

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

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

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

Винит Рейнольдс
источник
1

Вы можете использовать URLClassLoaderfor 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();
Панкадж Калра
источник