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

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

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


person abracadabra    schedule 20.11.2009    source источник


Ответы (4)


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

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

person David Roussel    schedule 20.11.2009
comment
Почему вы говорите, что ClassB должен находиться в пути к классам загрузчика ClassA (или родителей загрузчика ClassA)? Разве загрузчик ClassA не может переопределить _ 5_, чтобы он мог успешно загружать ClassB, даже если ClassB не указан в его пути к классам? - person Pacerier; 25.08.2014
comment
Действительно, не все загрузчики классов имеют путь к классам. Когда я писал, что ClassB должен находиться в пути к классам загрузчика классов ClassA, я имел в виду, что ClassB должен быть загружен загрузчиком классов ClassA. В 90% случаев они означают одно и то же. Но если вы не используете загрузчик классов на основе URL-адреса, тогда верен только второй случай. - person David Roussel; 26.08.2014
comment
Что значит сказать, что ClassA.class ссылается на ClassB.class? - person jameshfisher; 26.08.2014
comment
Когда ClassA имеет оператор импорта для ClassB, или если есть метод в ClassA, который имеет локальную переменную типа ClassB. Это приведет к загрузке ClassB, если он еще не загружен. - person David Roussel; 27.08.2014
comment
Думаю, моя проблема связана с этой темой. Что вы думаете о моем решении? Я знаю, что это плохой шаблон, но у меня нет другого представления, как его исправить: stackoverflow.com/questions/29238493/ - person Marcin Erbel; 25.03.2015

Это не отвечает на исходный вопрос, но, поскольку вопрос имеет высокий рейтинг и связан с любым ContextClassLoader запросом, я думаю, что важно ответить на связанный вопрос о том, когда следует использовать загрузчик класса контекста. Краткий ответ: никогда не используйте загрузчик классов контекста! Но установите его в getClass().getClassLoader(), когда вам нужно вызвать метод, у которого отсутствует параметр ClassLoader.

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

Если вы хотите создать класс с использованием отражения (например, при десериализации или загрузке настраиваемого именованного класса), библиотека, выполняющая отражение, должна всегда спрашивать приложение, какой загрузчик классов использовать, получая ClassLoader как параметр из приложения. Приложение (которое знает все классы, которые необходимо создать) должно передать его getClass().getClassLoader().

Любой другой способ получить загрузчик классов неверен. Если библиотека использует такие хаки, как _ 7_, _ 8_ или _ 9_ это ошибка, вызванная недостатком API. По сути, Thread.getContextClassLoader() существует только потому, что разработчик ObjectInputStream API забыл принять 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);
}
person yonran    schedule 25.03.2016
comment
Да, это ответ, который я хотел бы указать любому, кто задает вопрос. - person Marko Topolnik; 22.12.2016
comment
В этом ответе основное внимание уделяется использованию загрузчика классов для загрузки классов (для создания их экземпляров с помощью отражения или аналогичного), в то время как другая цель, для которой он используется (и, по сути, единственная цель, для которой я когда-либо использовал его лично), - это загрузка ресурсов. Применяется ли тот же принцип, или бывают ситуации, когда вы хотите получать ресурсы через загрузчик класса контекста, а не через загрузчик класса вызывающего абонента? - person Egor Hans; 18.11.2019
comment
Помните, что getClass().getClassLoader() не обязательно совпадает с ThisClass.class.getClassLoader(), кроме случаев, когда ThisClass равно final или вы не знаете, что подклассы не существуют. - person Jesse Glick; 06.07.2021

На infoworld.com есть статья, объясняющая разницу = ›Какой загрузчик классов следует использовать

(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 по умолчанию использует текущий загрузчик классов вызывающей стороны.
person Timour    schedule 20.11.2009
comment
Поскольку предлагается обходной путь состоит в том, чтобы заставить основные классы JNDI использовать загрузчики контекста потока, я не понимал, как это помогает в этом случае. Мы хотим загрузить классы поставщика реализации с помощью родительского загрузчика классов, но они не видны для родительского загрузчика классов . Итак, как мы можем загрузить их с помощью родительского элемента, даже если мы установим этот родительский загрузчик классов в контекстном загрузчике классов потока. - person Sunny Gupta; 07.08.2013
comment
@SAM, предлагаемый обходной путь на самом деле совершенно противоположен тому, что вы говорите в конце. В качестве загрузчика классов контекста устанавливается не родительский bootstrap загрузчик классов, а дочерний system загрузчик классов, с которым устанавливается Thread. Затем классы JNDI обязательно используют Thread.currentThread().getContextClassLoader() для загрузки классов реализации JNDI, доступных в пути к классам. - person Ravi K Thapliyal; 05.09.2013
comment
Обычное делегирование J2SE не работает, могу я узнать, почему оно не работает? Поскольку Bootstrap ClassLoader может загружать класс только из rt.jar и не может загружать класс из -classpath приложения? Верно? - person YuFeng Shen; 02.10.2018

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

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

Из блога javin paul на javarevisited:

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

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

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

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

Класс загружается в Java, когда это необходимо. Предположим, у вас есть специальный класс приложения под названием Abc.class, первый запрос загрузки этого класса поступит в Application ClassLoader, который будет делегировать его родительскому Extension ClassLoader, который далее делегирует загрузчику классов Primordial или Bootstrap.

  • Bootstrap ClassLoader отвечает за загрузку стандартных файлов классов JDK из rt.jar и является родительским для всех загрузчиков классов в Java. Загрузчик классов начальной загрузки не имеет родителей.

  • Extension ClassLoader делегирует запрос загрузки класса своему родительскому элементу, Bootstrap, и в случае неудачи загружает класс из каталога jre / lib / ext или любого другого каталога, указанного в системном свойстве java.ext.dirs.

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

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

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

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

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

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

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

person Ravindra babu    schedule 28.02.2016