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

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

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

Ответ 1

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

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

Ответ 2

Есть статья о 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, проверяются только в загрузчике и загрузчиках системных классов
  • API Java Serialization API по умолчанию использует загрузчик классов по умолчанию

Ответ 3

Это не отвечает на исходный вопрос, но поскольку вопрос очень ранжирован и связан для любого запроса 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() существует только потому, что тот, кто проектировал API ObjectInputStream забыл принять 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);
}

Ответ 4

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

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

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

enter image description here

enter image description here

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

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

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

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

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

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

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

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

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

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

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

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