Введение
На этот раз я представляю загрузчик классов, который содержит много контента, включая родительское делегирование, анализ кода, spi и т. д. Надеюсь, вы хорошо проведете время, изучая
Поддерживающее видео объяснение:воооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо(Обновления могут запаздывать, но не отсутствовать)
Предварительное понимание
роль загрузчика классов
Мы знаем, что java-код должен быть сначала скомпилирован в байт-код класса, а затем загружен в jvm.Чем он загружается? Эта "вещь" является загрузчиком классов
Классификация загрузчика классов
Загрузчики классов, предоставляемые jdk,
- Bootstrap ClassLoader
- Расширение ClassLoader
- Загрузчик классов приложений
Разделение труда загрузчика классов
Разделение труда: фактически каждый загрузчик классов загружает контент только по определенному пути.
загрузчик класса приложения: загрузить все файлы классов в ClassPath. Подробнее
Загрузчик класса расширения:Загрузка jre/lib/ext/
Запустите загрузчик классовЗагрузка /jre/lib/rt.jar
родительская делегация
Поговорите с классовым погрузчиком, мы должны упомянуть «Механизм делегата родителей», родители делегируют слово звуки неясным, на самом деле очень просто. Как будет оценено, сотрудничество между режимом классов погрузчика, понимая следующий текст в сочетании с фиг.
Если вам нужно загрузить класс сейчас (например, написать новый тест(), то загрузите класс Test)
- Приложение ищет его в собственном кеше, если находит, возвращает напрямую (конец), а если не находит, то пусть его находит загрузчик класса расширения.
(Предположим, что загрузчик класса приложения сейчас не найден, поэтому достигнут загрузчик класса расширения)
- Загрузчик класса расширения также будет искать в своем собственном кеше. Если он может найти его, верните его напрямую. Если он не может найти его, пусть его найдет загрузчик запускаемого класса.
(Предположим, что загрузчик классов расширения сейчас не найден, поэтому пришло время запустить загрузчик классов)
- Загрузчик классов запуска также будет искать его в кеше, если найдет — вернется, если не найдет — пойдет по тому пути, за который отвечает (/jre/li/rt.jar). «Горшок» в загрузчик класса расширения
4. Расширенный загрузчик классов также будет искать путь, за который он отвечает (jre/lib/ext/), и если не найдет, то продолжать кидать горшок обратно загрузчику классов приложения
- Загрузчик классов приложения просматривает путь к классам и выдает исключение ClassNotFound, если оно не найдено.
Советы:Отношения родитель-потомок в загрузчике классов являются логическими отношениями родитель-потомок, а не расширением в коде.
Почему родительское делегирование
Безопасно, безопасно, еще безопасно.
Десятки тысяч строк кода, безопасность превыше всего.
Только представьте, что если вы сами определяете класс java.lang.String и кладете его в путь к классам, очень опасно, если он напрямую загружается в память загрузчиком классов приложения, потому что это означает, что вы можете заменить исходный jdk в таким образом.Класс String. Затем проделайте из него какие-нибудь трюки, например, (переведите деньги на свою банковскую карту)
посмотрите на исходный код
Мы делаем еще один шаг, изучая исходный код и глубоко понимая механизм родительского делегирования. Далее я вырезаю код положения ключа для пояснения. Пожалуйста, объедините теоретическое содержание первой половины, чтобы понять
С++ код
Поскольку java работает на jvm, чтобы приблизиться к источнику проблемы, давайте посмотрим на код части c++.
кредитMainCLass: судя по названию, это класс, который загружает основной метод в наш java-код. (файл java.c)
После входа в loadMainCLass видно, что вызывается метод GetStaticMethodID и передается параметр checkAndLoadMain, это водораздел, а дальше он входит в java-код.
Еще одно слово:GetStaticMethodID получает идентификатор основной статической функции.
Java-часть
Тип scloader — AppClassLoader (загрузчик класса приложения).
Давайте сначала посмотрим на AppClassLoader
Он наследует UrlClassLoader
UrlClassLoader наследует SecureClassLoader
SecureClassLoader наследует ClassLoader
разбираться
Возвращаясь к предыдущему loadClass, AppClassLoader, URLClassLoader, SecureClassLoader, ни один из них не переписывал loadClass, поэтому в настоящее время вызывается loadClass в ClassLoader.
Давайте посмотрим на loadClass
Вызывается LoadClass(name, false), этот метод был переопределен AppClassLoader, поэтому в центре внимания AppClassLoader находится LoadClass
Наконец, этот метод вызывает LoadClass родительского класса, которым является ClassLoader.
Ниже приведен метод LoadClass в ClassLoader, Лично я считаю, что следующий код особенно доработан. Давайте вместе поклоняться мышлению великого бога
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
findLoadedClass: найти загруженный класс, поскольку он еще не загружен, поэтому здесь он должен быть нулевым.
Позвольте «родительскому» загрузчику загрузиться, вы можете видеть, что тип родительского загрузчика — ExtClassLoader (загрузчик расширенного класса)
Давайте посмотрим на код ExtClassLoader.
Его отношение наследования очень похоже на AppClassLoader.
Вернитесь к parent.loadClass, потому что ExtClassLoader также наследует ClassLoader, поэтому он, наконец, выполняется в этом месте, но обратите внимание, что в данный момент это ExtClassLoader.
Очевидно, что findLoadedClass ExtClassLoader также имеет значение null, поэтому он перейдет к findBootstrapClassOrNull.
Советы:Родительским загрузчиком ExtClassLoader является BootStrap, написанный на C++ Как видно из рисунка ниже, findBootstrapClassOrNull, наконец, вызывает нативный метод
Класс, который будет загружен в это время, написан нами и должен быть загружен загрузчиком классов приложения, поэтому findBootstrapClassOrNull вызовет исключение ClassNotFoundException.
После того, как исключение перехвачено, ExtClassloader пытается загрузить класс в каталог, за который он отвечает, но, естественно, не может его загрузить и в конце выдает исключение
После того, как загрузчик класса приложения поймает исключение, он также попытается загрузить сам класс (из пути к классам), потому что наш класс существует в пути к классам, поэтому его можно загрузить, иначе он выдаст исключение ClassNotFoundException.
На данный момент код, делегированный родителями, в основном тот же. Давайте еще раз посмотрим, как родителю присваивается значение
вернуться к исходной начальной точке
Посмотрите, где определен scloader,
Посмотрите еще раз на getSystemClassLoader
Внутри есть строка sun.misc.Launcher.getLauncher();
см. конструктор
ExtClassLoader и AppClassLoader создаются здесь
Из этого вы можете видеть, что родитель ExtClassLoader имеет значение null
Родителем AppClassLoader является ExtClassLoader.
Код для этой важной части почти такой же
spi
Обход родительской делегирования
Прежде чем говорить о SPI, разберемся с механизмом: когда наш класс A использует другой класс B, предполагая, что этот класс B ранее не был загружен, то в это время должен быть загружен класс B, а для загрузки класса следует использовать загрузчик классов Б., тогда какой загрузчик использовать? Фактически используется загрузчик класса А.
Тогда это создаст проблему (в качестве примера возьмем jdbc)
Драйвер загружается классом запуска в rt.jar, но драйверы базы данных, предоставляемые различными производителями, реализованы в пути к классам, и загрузчик класса запуска не может быть загружен.
Чтобы решить эту проблему, вы можете использовать spi для обхода родительского делегирования.Когда мы смотрим на исходный код, мы фактически получаем загрузчик класса контекста потока.
демонстрация спи
Простая демонстрация того, как использовать spi, без конкретных объяснений (поскольку это не является предметом этой статьи).
написать интерфейс
Напишите любой класс реализации
Создайте новый файл в этом каталоге, имя — это полное имя класса интерфейса, а содержимое — это полное имя класса класса реализации.
Просто проверьте это
На этот раз все, пока.