Java, Classpath, Classloading => Кілька версій одного банку / проекту


117

Я знаю, що це може бути дурним питанням для досвідчених кодерів. Але у мене є бібліотека (http-клієнт), якої потребують деякі інші рамки / банки, використовувані в моєму проекті. Але всі вони потребують різних основних версій, таких як:

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

Чи достатньо розумний завантажувач класів, щоб їх якось відокремити? Швидше за все, ні? Як поводиться з цим завантажувач класів, якщо клас однаковий у всіх трьох банках. Який з них завантажений і чому?

Чи завантажувач класів набирає лише одну банку або змішує класи довільно? Так, наприклад, якщо клас завантажений з версії 1.jar, всі інші класи, завантажені з того самого завантажувача, всі перейдуть в одну банку?

Як ви вирішуєте цю проблему?

Чи є якийсь трюк, щоб якось "включити" банки в "Requir.jar", щоб вони розглядалися як "одна одиниця / пакет" з боку Classloader, або якимось чином пов'язані?

Відповіді:


57

Проблеми, пов'язані з завантажувачем, є досить складною справою. У будь-якому випадку слід пам’ятати про деякі факти:

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

Я завжди вважав, що налаштування навантажувача класів є складним завданням. Я б краще запропонувати уникати декількох несумісних залежностей, якщо можливо.


13
Завантажувач класу завантаження завантажує відповідні. Коли ви створюєте інстанціювання нового класу, викликається більш конкретний завантажувач. Якщо він не знаходить посилання на клас, який ви намагаєтеся завантажити, він делегує своєму батькові. Будь ласка, майте мене, але це залежить від політики завантажувача класів, яка за замовчуванням є батьківською першою. Іншими словами, дочірній клас спочатку попросить свого батька завантажити клас і завантажиться лише в тому випадку, якщо вся ієрархія не змогла його завантажити, ні ??
deckingraj

5
Ні - зазвичай завантажувач класів делегує своєму батькові, перш ніж шукати клас. Дивіться клас javadoc для Classloader.
Джо Керні

1
Я думаю, що tomcat робить це так, як описано тут, але "звичайна" делегація - це спочатку запитати у батьків
rogerdpack

@deckingraj: після деякого googling я виявив це в документах Oracle: "У дизайні делегування завантажувач класів делегує завантаження класу своєму батькові перед спробою завантаження самого класу. [...] Якщо завантажувач батьківського класу не може завантажити клас, завантажувач класів намагається завантажити сам клас. Фактично, завантажувач класів відповідає за завантаження тільки тих класів, які недоступні батьківському ". Я буду досліджувати далі. Якщо це з'явиться як реалізація за замовчуванням, я відповідно оновлю відповідь. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Лука Пуцу

20

Кожне заняття набирає рівно один клас. Зазвичай перший знайдений.

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


6

Завантажувач класів завантажить класи з jar, який трапився першим на classpath. Зазвичай несумісні версії бібліотеки мають різницю в пакунках, але навряд чи вони дійсно несумісні і не можуть бути замінені однією - спробуйте jarjar.


6

Класонавантажувачі класу на вимогу. Це означає, що клас, необхідний спочатку вашою програмою та відповідними бібліотеками, буде завантажений перед іншими класами; запит на завантаження залежних класів, як правило, видається під час завантаження та зв'язування залежного класу.

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

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


1

Ви можете використовувати 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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.