Когда задействованы загрузчики классов Java?
Там 10 миллионов статей и документов там, на каких Java-загрузчиках классов, и как /* почему * писать свои собственные... но все они, кажется, принимают некоторые вещи, на которые я не могу найти простой ответ!
Я понимаю работу classloader: читать байт-код и строить из него объект. Разные загрузчики классов делают это по-другому и т.д.
Но, когда мне никогда не приходилось кодировать API-интерфейс загрузчика классов в моем собственном коде, и мне никогда не приходилось писать один из моих собственных, я испытываю огромные трудности в понимании , когда на самом деле срабатывает собственный код ClassLoader
.
Например:
public static void main(String[] args) {
Fizz fizz = new Fizz();
fuzz.buzz();
}
Здесь мы имеем объект Fizz
. Перед тем, как Fizz
можно создать экземпляр, нам понадобится загрузчик классов для загрузки и загрузки Fizz.class
в его кеш. Где и когда это происходит?!?! Это явно не указано в моем коде, поэтому он должен неявно находиться где-то в JRE...?
Тангенциально к этому вопросу, если я пишу свой собственный загрузчик классов, скажем, WidgetClassLoader
и хочу настроить его для загрузки всех моих классов приложений или, возможно, только моего Fizz.class
, как мне "связать" этот WidgetClassLoader
в мое приложение, чтобы он знал, какой загрузчик классов использовать? Должен ли мой код явно вызывать этот загрузчик классов или он будет неявным, как в первом примере? Спасибо заранее!
Ответы
Ответ 1
Вы задаете вопрос не так тривиально, как думаете сейчас.
Ваш пример Fizz:
Когда загружается Fizz? Это определено в JLS (Глава 5.4: Связывание). Он не определяет, когда загружается Fizz, но дает гарантии относительно видимого поведения. Для части "когда", если Fizz не может быть найден, исключение будет выбрано из первого оператора, который обращается к Fizz (Fizz fizz = new Fizz()). Я уверен, что в этом случае это будет новый Fizz(), потому что правая часть выражения сначала просверливается. Если вы написали это так:
Fizz fizz = null;
fizz = new Fizz();
В этом случае Fizz fizz = null уже выбрасывает исключение, потому что это первый доступ к классу Fizz.
Кто загружает Fizz? Когда класс должен быть загружен, классный загрузчик, который "принадлежит" коду, требующему класс, используется для получения класса. В примере Fizz это будет загрузчик классов, который загрузил класс основным методом. Конечно, загрузчик классов может выбрать делегировать его родительскому загрузчику классов, если он не может самостоятельно загружать Fizz.
Как мне заставить JVM использовать мой ClassLoader? Есть два пути, явно или неявно. Явно: вы можете загрузить класс через свой собственный загрузчик классов, вызвав его методы. Implcitly: когда вы выполняете код (то есть методы или инициализаторы) из класса, который уже был загружен из вашего загрузчика классов, и эта ссылка должна быть разрешена в процессе, ваш загрузчик классов будет автоматически использоваться, потому что это загрузчик классов, загружающий код в на первом месте.
Ответ 2
Java имеет загрузчик классов по умолчанию. Это ищет объявления классов в пути класса по умолчанию. Если вы пишете свой собственный загрузчик классов, вы можете (и должны) установить загрузчик родительского класса. Это будет по умолчанию, если у вас нет другого. Если yuo не делает этого, ваш загрузчик классов не сможет найти классы API Java. Если Java ищет класс, он не начинает искать с вами пользовательский загрузчик классов, но с загрузчиком родительского класса. Если у этого есть родитель, он начинается там и так далее. Только в том случае, если класс не найден, следующий загрузчик дочерних классов используется для повторного запуска. Снова это продолжается, пока есть дети. Если класс не найден ни одним из загрузчиков в цепочке, бросается ClassNotFoundException
.
Конечно, java использует ваш загрузчик классов, если вы сначала установите его как загрузчик класса по умолчанию (вызывая Thread.currentThread().setContextClassLoader()
) или загрузите класс вручную (вызывая loadClass()
).
Я не уверен, когда вызывается загрузчик класса. Я думаю, что он вызывается либо при запуске программы (для всех классов, объявленных как import
), либо при первом использовании класса (объявление переменной или вызов конструктора).
Ответ 3
Фактическое создание класса происходит в defineClass
. Класс создается с использованием байтового массива из любого из нескольких источников.
Нормальный путь до defineClass
(который protected
) проходит через findClass
(что, конечно же, также protected
). Таким образом, обычная точка входа loadClass
→ findClass
→ defineClass
. Но есть и другие пути для особых случаев.
(Все это довольно запутанно и представляет собой историю добавления слоев, поскольку защита стала более сложной, а способы доступа более разнообразны.)
Ответ 4
Если вам интересны загрузчики классов и когда и как они работают, вы также можете проверить спецификацию OSGi - мне кажется это будет очень интересным для вас. OSGi - это основа для Java, которая обеспечивает модульность, четкое разделение кода и управление жизненным циклом и которая очень популярна на данный момент (например, сам Eclipse основан на одном).
OSGi сильно использует загрузчики классов, и есть очень приятное объяснение, когда и как все с загрузкой классов происходит внутри спецификации. В основном у них есть отдельный блок-загрузчик классов для каждого пакета (который называется модулем), и эти загрузчики классов заботятся о зависимостях и выбирают правильный класс из другого пакета.