Разница между загрузчиком классов контекста потока и обычным загрузчиком классов

242

В чем разница между загрузчиком классов контекста потока и обычным загрузчиком классов?

То есть, если Thread.currentThread().getContextClassLoader()и getClass().getClassLoader()вернуть разные объекты загрузчика классов, какой из них будет использоваться?

абракадабра
источник

Ответы:

151

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

Загрузчик классов контекста потока является текущим загрузчиком классов для текущего потока. Объект может быть создан из класса ClassLoaderCи затем передан в поток, принадлежащий ClassLoaderD. В этом случае объект необходимо использовать Thread.currentThread().getContextClassLoader()напрямую, если он хочет загрузить ресурсы, которые недоступны в его собственном загрузчике классов.

Дэвид Руссел
источник
1
Почему вы говорите, что он ClassBдолжен быть на classpath 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/…
Марцин Эрбель
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 before (и затем сбросить его):

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);
}
yonran
источник
5
Да, это ответ, который я хотел бы указать любому, кто задает вопрос.
Марко Топольник
6
Этот ответ сфокусирован на использовании загрузчика классов для загрузки классов (для создания их экземпляров с помощью отражения или подобного), в то время как другой целью, для которой он используется (и фактически единственной целью, которую я когда-либо использовал для личной жизни), является загрузка ресурсов. Применяется ли тот же принцип или существуют ситуации, когда вы хотите получать ресурсы через загрузчик классов контекста, а не загрузчик классов вызывающих?
Егор Ганс
90

На javaworld.com есть статья, которая объясняет разницу => Какой ClassLoader вы должны использовать

(1)

Загрузчики классов контекста потока обеспечивают заднюю дверь вокруг схемы делегирования загрузки классов.

Возьмем, к примеру, JNDI: его внутренности реализованы классами начальной загрузки в rt.jar (начиная с J2SE 1.3), но эти основные классы JNDI могут загружать поставщиков JNDI, реализованных независимыми поставщиками и потенциально развернутых в -classpath приложения. В этом сценарии родительский загрузчик классов (в данном случае первичный) загружает класс, видимый для одного из его дочерних загрузчиков (например, системный). Обычное делегирование 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 использует текущий загрузчик классов вызывающего по умолчанию
Timour
источник
Поскольку предполагается, что обходной путь состоит в том, чтобы основные классы JNDI использовали загрузчики контекста потока, я не понял, как это помогает в этом случае. Мы хотим загрузить классы поставщика реализации, используя родительский загрузчик классов, но они не видимы для родительского загрузчика классов. , Итак, как мы можем загрузить их, используя parent, даже если мы установили этот родительский загрузчик классов в контекстном загрузчике классов потока.
Санни Гупта
6
@SAM, предлагаемый обходной путь на самом деле совершенно противоположен тому, что вы говорите в конце. В качестве bootstrapзагрузчика класса контекста устанавливается не родительский загрузчик классов, а загрузчик дочернего systemкласса classpath, с которым Threadон устанавливается. Затем JNDIклассы обязательно используют Thread.currentThread().getContextClassLoader()для загрузки классы реализации JNDI, доступные в пути к классам.
Рави Таплиял
«Нормальное делегирование J2SE не работает», могу я узнать, почему оно не работает? Потому что Bootstrap ClassLoader может загружать класс только из rt.jar и не может загружать класс из -classpath приложения? Правильно?
YuFeng Shen
37

Если добавить ответ @David Roussel, классы могут загружаться несколькими загрузчиками классов.

Давайте разберемся, как работает загрузчик классов .

Из блога Джавина Пола в javarevisited:

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

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

ClassLoader следует трем принципам.

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

Класс загружается в Java, когда это необходимо. Предположим, у вас есть специфичный для приложения класс с именем Abc.class, первый запрос на загрузку этого класса поступит в Application ClassLoader, который делегирует его родительскому расширению 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.

  • Загрузчик классов приложений является дочерним по отношению к Extension ClassLoader и реализуется sun.misc.Launcher$AppClassLoaderклассом.

ПРИМЕЧАНИЕ. За исключением загрузчика классов Bootstrap , который реализован на родном языке в основном на C, все загрузчики классов Java реализованы с использованием java.lang.ClassLoader.

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

Согласно принципу видимости, Child ClassLoader может видеть класс, загруженный Parent ClassLoader, но, наоборот, это не так.

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

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

Равиндра Бабу
источник