Різниця між завантажувачем контекстного класу потоку та звичайним завантажувачем класів


242

Чим відрізняється завантажувач контекстного класу потоку від звичайного завантажувача класів?

Тобто, якщо Thread.currentThread().getContextClassLoader()і getClass().getClassLoader()повертати об'єкти завантажувача різних класів, який із них буде використовуватися?

Відповіді:


151

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

Контент-завантажувач контексту потоку - це поточний завантажувач класів для поточного потоку. Об'єкт можна створити з класу в, ClassLoaderCа потім передати до потоку, яким належить ClassLoaderD. У цьому випадку об’єкт повинен використовувати Thread.currentThread().getContextClassLoader()безпосередньо, якщо він хоче завантажити ресурси, недоступні у власному завантажувачі класів.


1
Чому ти вважаєш, що це ClassBповинно бути на уроці ClassAнавантажувача (або ClassAбатьків навантажувача)? Хіба не можна ClassAперевантажувати навантажувач loadClass()таким чином, щоб він міг успішно завантажуватися ClassBнавіть тоді, коли ClassBвін не перебуває на своєму шляху?
Pacerier

8
Дійсно, не всі завантажувачі класів мають шлях. Коли я писав: "ClassB повинен бути на шляху руху завантажувача ClassA", я мав на увазі "ClassB потрібно завантажувати завантажувачем класу ClassA". 90% часу вони означають те саме. Але якщо ви не використовуєте завантажувач, що базується на URL-адресі, то справедливим є лише другий випадок.
Девід Руссель

Що означає сказати, що " ClassA.classпосилання ClassB.class"?
jameshfisher

1
Коли ClassA має заяву про імпорт для ClassB, або якщо в ClassA є метод, який має локальну змінну типу ClassB. Це призведе до завантаження ClassB, якщо він ще не завантажений.
Девід Руссел

Я думаю, моя проблема пов'язана з цією темою. Що ти думаєш про мою розпусту? Я знаю , що це не дуже гарна картина, але у мене немає ніякої іншої ідеї , як це виправити: stackoverflow.com/questions/29238493 / ...
Marcin Ербель

109

Це не дає відповіді на оригінальне запитання, але оскільки це питання високо ранговане та пов'язане для будь-якого ContextClassLoaderзапиту, я думаю, що важливо відповісти на відповідне питання про те, коли слід використовувати навантажувач контекстного класу. Коротка відповідь: ніколи не використовуйте завантажувач контекстного класу ! Але встановіть це, getClass().getClassLoader()коли вам доведеться викликати метод, у якого відсутній ClassLoaderпараметр.

Коли код з одного класу вимагає завантажити інший клас, правильний завантажувач класів для використання - це той же завантажувач класів, що і клас виклику (тобто getClass().getClassLoader()). Так працює 99,9% часу, тому що це те, що JVM робить сам при першому конструюванні екземпляра нового класу, виклику статичного методу або доступу до статичного поля.

Коли ви хочете створити клас за допомогою відображення (наприклад, при десеріалізації або завантаженні настроюваного імені класу), бібліотека, яка робить відображення, завжди повинна запитувати додаток, яким завантажувачем класів користуватися, отримуючи ClassLoaderяк параметр від програми. Додаток (який знає всі класи, які потребують побудови) повинен пройти його getClass().getClassLoader().

Будь-який інший спосіб отримати навантажувач класу невірно. Якщо бібліотека використовує хаки, такі як Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader()або sun.reflect.Reflection.getCallerClass()це помилка, викликана дефіцитом в API. В основному, Thread.getContextClassLoader()існує лише тому, що той, хто розробляв ObjectInputStreamAPI, забув прийняти цей ClassLoaderпараметр, і ця помилка переслідувала спільноту Java донині.

Однак, багато хто з класів JDK використовують один із кількох хаків, щоб відгадати якийсь завантажувач класів. Деякі використовують ContextClassLoader(що не вдається, коли ви запускаєте різні додатки в спільному пулі потоків або коли ви виходите з нього ContextClassLoader null), деякі вигулюєте стек (який виходить з ладу, коли прямий абонент класу сам є бібліотекою), деякі використовують завантажувач системного класу (що добре, якщо документально використовувати лише класи в CLASSPATH) або завантажувач класу завантажувача, а деякі використовують непередбачувану комбінацію вищезазначених методів (що лише робить речі більш заплутаними). Це призвело до сильного плачу та скрегіт зубів.

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

ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
    // call some API that uses reflection without taking ClassLoader param
} finally {
    Thread.currentThread().setContextClassLoader(originalClassLoader);
}

5
Так, це відповідь, яку я хотів би зазначити кожному, хто задає це питання.
Марко Топольник

6
Ця відповідь зосереджена на використанні завантажувача класів для завантаження класів (для отримання екземплярів через відображення або подібне), тоді як інша мета, для якої він використовується (і, власне, єдина мета, яку я коли-небудь використовував це особисто) - завантаження ресурсів. Чи застосовується той самий принцип, чи існують ситуації, коли ви хочете отримати ресурси через завантажувач контекстного класу, а не завантажувач класу?
Єгор Ганс

90

На сайті javaworld.com є стаття, яка пояснює різницю => Який ClassLoader слід використовувати

(1)

Конструктори навантажувачів контексту мають нитку навколо схеми делегування класів.

Візьмемо для прикладу JNDI: його кишки реалізовані класами завантаження у rt.jar (починаючи з J2SE 1.3), але ці основні класи JNDI можуть завантажувати провайдерів JNDI, реалізованих незалежними постачальниками та потенційно розгорнутими в клас-додатку програми. Цей сценарій вимагає, щоб батьківський завантажувач класів (первинний в даному випадку) завантажував клас, видимий одному з його дочірніх завантажувачів (наприклад, системний). Звичайна делегація J2SE не працює, і вирішення полягає в тому, щоб змусити основні класи JNDI використовувати потокові контекстні завантажувачі, таким чином ефективно "тунелюючи" через ієрархію завантажувачів класів у напрямку, протилежному правильному делегуванню.

(2) з того самого джерела:

Ця плутанина, ймовірно, залишиться з Java ще деякий час. Візьміть будь-який J2SE API з динамічним завантаженням ресурсів будь-якого типу та спробуйте відгадати, яку стратегію завантаження він використовує. Ось вибірка:

  • JNDI використовує завантажувачі контексту
  • Class.getResource () та Class.forName () використовують поточний завантажувач класів
  • JAXP використовує завантажувачі контекстних класів (станом на J2SE 1.4)
  • java.util.ResourceBundle використовує поточний завантажувач класу абонента
  • Обробники протоколів URL, вказані за допомогою системного властивості java.protocol.handler.pkgs, шукаються лише у завантажувальному пристрої та завантажувачах системних класів
  • Java Serialization API використовує поточний завантажувач класу абонента за замовчуванням

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

6
@SAM, запропонований спосіб вирішення насправді протилежний тому, що ви говорите в кінці. Це не завантажувач батьківського bootstrapкласу як завантажувач контекстного класу, а завантажувач дочірнього systemкласового класу, з Threadяким встановлюється. Потім JNDIкласи обов'язково використовуються Thread.currentThread().getContextClassLoader()для завантаження класів реалізації JNDI, доступних на classpath.
Раві Тапліял

"Звичайна делегація J2SE не працює", може я знаю, чому вона не працює? Тому що Bootstrap ClassLoader може завантажувати клас лише з rt.jar і не може завантажити клас із -classpath програми? Правильно?
ЮФен Шен

37

Додавши до @David Roussel відповіді, класи можуть завантажуватися кількома навантажувачами класів.

Давайте зрозуміємо, як працює навантажувач класу .

З блогу javin paul в javarevisited:

введіть тут опис зображення

введіть тут опис зображення

ClassLoader дотримується трьох принципів.

Принцип делегування

Клас завантажується в Java, коли це потрібно. Припустимо, у вас є специфічний для програми клас під назвою Abc.class, перший запит про завантаження цього класу надійде до Application ClassLoader, який делегує своєму батьківському Extell ClassLoader, який надалі делегує завантажувач класів Primordial або Bootstrap.

  • Bootstrap ClassLoader відповідає за завантаження стандартних файлів класу JDK від rt.jar, і він є батьківським для всіх завантажувачів класів на Java. У завантажувача класу Bootstrap немає батьків.

  • Розширення ClassLoader делегує запит на завантаження класу своєму батьківському, Bootstrap, і якщо це не вдалося, завантажує клас у каталог jre / lib / ext або будь-який інший каталог, на який вказує властивість системи java.ext.dirs

  • Завантажувач системного або додаткового класу, і він несе відповідальність за завантаження специфічних класів додатків із змінної середовища CLASSPATH, опції командного рядка -classpath або -cp, атрибута Class-Path файлу Manifest всередині JAR.

  • Навантажувач класів додатків - це дочірнє розширення ClassLoader та його реалізовано sun.misc.Launcher$AppClassLoaderкласом.

ПРИМІТКА: За винятком завантажувача класів Bootstrap , який реалізований рідною мовою, головним чином на C, усі завантажувачі класів Java реалізовані за допомогою java.lang.ClassLoader.

Принцип видимості

Відповідно до принципу видимості, Child ClassLoader може бачити клас, завантажений Parent ClassLoader, але навпаки, це неправда.

Принцип унікальності

Відповідно до цього принципу, клас, завантажений Parent, не повинен завантажуватися Child ClassLoader знову

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.