Разница между загрузчиком классов контекста потока и обычным загрузчиком классов
В чем разница между загрузчиком классов контекста потока и обычным загрузчиком классов?
То есть если Thread.currentThread().getContextClassLoader()
а также getClass().getClassLoader()
вернуть разные объекты загрузчика классов, какой из них будет использоваться?
4 ответа
Каждый класс будет использовать свой собственный загрузчик классов для загрузки других классов. Так что если ClassA.class
Рекомендации ClassB.class
затем ClassB
должен быть на пути к классам загрузчика классов ClassA
или его родители.
Загрузчик классов контекста потока является текущим загрузчиком классов для текущего потока. Объект может быть создан из класса в ClassLoaderC
а затем перешли к теме, принадлежащей ClassLoaderD
, В этом случае объект должен использовать Thread.currentThread().getContextClassLoader()
напрямую, если он хочет загрузить ресурсы, которые недоступны в его собственном загрузчике классов.
Это не отвечает на первоначальный вопрос, но так как вопрос высоко оценивается и связан для любого 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()
существует только потому, что тот, кто спроектировал 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);
}
На 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 использует текущий загрузчик классов вызывающего по умолчанию
Если добавить ответ @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