Резюме механизма загрузки классов

Java

Мы знаем, что для запуска java нужно скомпилировать и запустить, а javac компилирует исходный код java в файл класса. Виртуальная машина загружает данные, описывающие класс, из файла класса в память, проверяет, преобразует, анализирует и инициализирует данные и, наконец, формирует тип java, который может быть непосредственно использован виртуальной машиной.механизм загрузки классов, который он завершает во время бега.

JVM может загружать файлы классов в память двумя способами:

  • Неявная загрузка: виртуальная машина автоматически загружает необходимые классы
  • Явная загрузка: код загружается путем вызова класса ClassLoader, такого как Class.forName(), this.getClass.getClassLoader().loadClass() или findClass(), который сам реализует ClassLoader.

Далее рассмотрим три примера

Раньше я знал только, что статические вещи будут инициализированы до создания объекта, а также знал, что инициализация начинается с родительского класса, но никогда не понимал, почему именно в таком порядке, пока не понял, как виртуальная машина реализует загрузку классов . Прежде чем мы начнем по-настоящему понимать загрузку классов, давайте рассмотрим три примера.

Первый

class SuperClass {
    static{
        System.out.println("SuperClass Init");
    }
    public static int value = 123;
}

class SubClass extends SuperClass{
    static{
        System.out.println("SubClass Init");
    }
}

public class NotInitialization{
    public static void main(String agrs[]){
        System.out.println(SubClass.value);
    }
}

вывод:

SuperClass Init
123

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

второй

class SuperClass {
    static{
        System.out.println("SuperClass Init");
    }
    public static int value = 123;
}

class SubClass extends SuperClass{
    static{
        System.out.println("SubClass Init");
    }
}

public class NotInitialization{
    public static void main(String agrs[]){
        SuperClass[] sca = new SuperClass[10];
    }
}

вывод:

//无输出

Да, после запуска нет вывода, но он запускает инициализацию класса под названием «[Lorg.fenixsoft.classloading.SuperClass», иДействие создания запускается инструкцией байт-кода newarray., отсюда мы подошли к реальной ситуации создания массива объектов

Третий

class ConstClass{
    static{
        System.out.println("ConstClass init");
    }

    public static final String WORD = "Hello";
}

public class NotInitialization{
    public static void main(String agrs[]){
        System.out.println(ConstClass.WORD);
    }
}

вывод:

Hello

Здесь WORD — это константа, которая была сгенерирована на этапе компиляции, что означает, что этап компиляции был оптимизирован путем распространения констант и сохранен в пуле констант класса NotInitialization.Все последующие ссылки на него являются ссылками на класс постоянный пул NotInitialization., поэтому класс не инициализируется.

инициализация класса

Вот краткое изложение пяти ситуаций, в которых класс должен быть инициализирован:

  • Встречайте четыре инструкции байт-кода new, getstatic, putstatic, invokestatic (последние три можно просто понимать как вызов статического свойства или метода).
  • При использовании метода пакета java.lang.reflect для вызова отражения класса
  • Родительский класс при инициализации класса не инициализируется при инициализации родительского класса
  • При запуске виртуальной машины пользователю необходимо выполнить основной класс (класс основного метода)
  • Когда поддерживается динамический язык JDK1.7 (проверка типов выполняется во время выполнения, а не во время компиляции, пакет java.lang.invoke, вот еще один момент, динамический язык и отражение отличаются)

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

процесс загрузки класса

Давайте посмотрим, что делать во всем процессе загрузки класса

image

нагрузка

В этот период нужно сделать три вещи:

  1. Получить двоичный поток этого класса по полному имени
  2. Преобразуйте статическую структуру хранения, представленную этим потоком байтов, в структуру данных времени выполнения области метода.
  3. Создайте объект java.lang.Class, представляющий этот класс в памяти в качестве интерфейса доступа к различным данным этого класса в области методов.

Здесь фаза загрузки для классов, не являющихся массивами, немного отличается от классов массивов:

  • Для классов без массива
    • Фаза загрузки может быть завершена загрузчиком классов начальной загрузки, предоставляемым системой, или определяемым пользователем загрузчиком классов, или вы можете самостоятельно контролировать способ получения потока байтов (перепишите метод loadClass для загрузки класса)
  • для класса массива
    • Если тип компонента массива является ссылочным типом, массив идентифицируется в пространстве имен класса загрузчика классов, который загрузил тип компонента.
    • Если тип компонента не является ссылочным, массив будет помечен как связанный с загрузчиком классов начальной загрузки.
    • Видимость класса массива такая же, как видимость его типа компонента, тогда значение по умолчанию общедоступно.

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

проверять

Цель этого шага — убедиться, что информация, содержащаяся в потоке байтов файла Class, соответствует требованиям текущей виртуальной машины и не будет угрожать безопасности самой виртуальной машины.Он примерно разделен на четыре проверки.

  • Проверка формата файла: соответствует ли он спецификации формата файла класса и может ли он обрабатываться текущей версией виртуальной машины, чтобы убедиться, что входной поток байтов может быть правильно проанализирован и сохранен в области методов, а формат соответствует требованиям информации типа Java
  • Проверка метаданных: выполняется семантическая проверка информации метаданных класса, чтобы убедиться, что информация метаданных не соответствует спецификации языка Java.
  • Проверка байт-кода: посредством анализа потока данных и потока управления определяется, что семантика программы является законной и логичной.
  • Проверка символьной ссылки: убедитесь, что действие синтаксического анализа выполняется нормально.

Подготовить

Выделите память для переменных класса и установите значение инициализации переменных класса, а также выделите их в области методов.Например, int равно 0, логическое значение равно false, а ссылка равна нулю.

Разобрать

Процесс замены символических ссылок в пуле констант прямыми ссылками

Q, что такое символическая ссылка и что такое прямая ссылка?

Мое понимание:

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

Синтаксический анализ в основном включает ссылки на символы, такие как классы, интерфейсы, поля, методы классов, методы интерфейса, типы методов, дескрипторы методов и квалификаторы сайта вызова.

инициализация

Выполняются статические инициализаторы, содержащиеся в классе. В конце этой фазы статические поля инициализируются значениями по умолчанию. Инициализация следует следующим принципам (которые представляют собой инструкции байт-кода для инициализации класса)

  • В блоке статической инициализации доступны только переменные, определенные до блока статических операторов; переменным, определенным после него, могут быть присвоены значения в предыдущем блоке статических операторов, но к ним нельзя получить доступ
  • Виртуальная машина гарантированно запустится раньше, чем подкласс суперкласса.
  • Виртуальная машина обеспечивает правильную блокировку и синхронизацию методов класса в многопоточной среде.

Вот несколько примеров

public class Test {
    static {
        i = 0;
        //System.out.println(i);
    }
    static int i;
}

Строка, закомментированная выше, сообщит об ошибке, поскольку в статическом блоке инициализации доступны только переменные, определенные до статического блока операторов; переменным, определенным после него, можно присвоить значения в предыдущем статическом блоке операторов, но к ним нельзя получить доступ , что указывает на то, что первая полоса

public class Test {
    static class DeadLoopClass{
        static{
            if (true){
                System.out.println(Thread.currentThread() + "init DeadLoopClass");
                while(true){
                }
            }
        }
    }

    public static void main(String agrs[]){
        Runnable script = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread() + "start");
                DeadLoopClass dlc = new DeadLoopClass();
                System.out.println(Thread.currentThread() + "run over");
            }
        };
        Thread t1 = new Thread(script);
        Thread t2 = new Thread(script);
        t1.start();
        t2.start();
    }
}

выход

Thread[Thread-0,5,main]start
Thread[Thread-1,5,main]start
Thread[Thread-0,5,main]init DeadLoopClass

Он напечатает приведенный выше оператор и заблокируется. Этот пример показывает, что инициализация гарантирует, что класс будет правильно заблокирован.

загрузчик классов

Далее рассмотрим характеристики загрузчика классов, его функция заключается в динамической загрузке класса в пространство памяти виртуальной машины Java, что и является упомянутым выше «получением бинарного слова, описывающего этот класс, через полностью определенное имя класса "троттлинг",И это действие реализуется вне виртуальной машины Java, то есть приложение решает, как получить требуемые классы.

Классы и загрузчики классов

Есть два необходимых условия для определения того, являются ли два объекта класса одним и тем же объектом класса в JVM.

  • Полное имя класса должно быть одинаковым, включая имя пакета.
  • ClassLoader (относится к объекту экземпляра ClassLoader), который загружает этот класс, должен быть тем же
Какова динамика загрузки классов?

Приложение всегда состоит из более чем n классов.При запуске Java-программы она не загружает все классы одновременно, а затем запускает ее.Она всегда загружает базовые классы, обеспечивающие запуск программы в jvm за один раз. время и другие классы.Подождите, пока jvm будет использован, а затем загрузите его, преимущество этого заключается в экономии памяти

Модель родительского делегирования

Загрузчики классов можно условно разделить на три категории:

  • Bootstrap ClassLoader: Этот загрузчик написан на C++, инициализируется после запуска виртуальной машины Java и отвечает заЗагрузите %JAVA_HOME%/jre/lib, путь, указанный параметром -Xbootclasspath, и классы в %JAVA_HOME%/jre/classes
  • Расширение ClassLoader: реализовано sum.misc.Launcher$ExtClassLoader, отвечает заЗагрузите %JAVA_HOME%/jre/lib/ext, все каталоги классов по этому пути и библиотеки классов по пути, указанному системной переменной java.ext.dirs
  • Application ClassLoader (Application ClassLoader): реализуется sun.misc.Launcher$AppClassLoader, отвечает за загрузку библиотеки классов, указанной в пользовательском пути к классам, родительским классом является ExtensionClassLoader.
  • пользовательский загрузчик классов
    image

Так что же такое родительская модель делегирования? Давайте сначала посмотрим на его рабочий процесс.

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

Обратите внимание, что родители вызываются здесь не из-за отношения наследования, а из-за отношения комбинации.

Преимущества модели родительского делегирования

Легко думать, что иерархия родительской модели делегирования позволяет избежать повторной загрузки, особенно основная библиотека классов java не будет заменена.Например, если вы самостоятельно определите java.lang.Integer, родительская модель делегирования будет не инициализировать его, а вернуть напрямую Loaded Integer.class. Конечно, если вы принудительно используете метод defineClass() (этот метод анализирует поток байтов в объект класса, который может распознать JVM) для загрузки класса, начинающегося с java.lang, это не удастся, и будет создано исключение безопасности. брошенный.

Реализация кода модели родительского делегирования

Класс loadClass() ClassLoader, только перечисляет ключ

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
        //首先,检查请求的类是否已经被加载过了
        Class c = findLoadedClass(name);
        if (c == null){
            try{
                if (parent != null){
                    c = parent.loadClass(name,false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e){
                //如果父类加载器抛出ClassNotFoundException,说明父类加载器无法完成加载请求
            }
            if (c == null){
                //在父类加载器无法加载的时候
                //再调用本身的findClass方法来进行类加载
                c = findClass(name);
            }
        }
        if (resolve){
            //使用类的Class对象创建完成也同时被解析
            resolveClass(c);
        }
        return c;
    }

FindClass() из ClassLoader,

//直接抛出异常
protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
}

определить класс ClassLoader

protected Class<?> findClass(String name) throws ClassNotFoundException {
        //获取类的class文件字节数组
        byte[] classData = getClassData(name);
        if (classData == null){
            throw new ClassNotFoundException();
        } else {
            //直接生成class对象
            return defineClass(name,classData,0,classData.length);
        }
    }

resolveClass() из ClassLoader

protected final void resolveClass(Class<?> c) {
        if (c == null) {
            throw new NullPointerException();
        }
    }

Давайте посмотрим на конкретную роль ключевых методов:

  • loadClass(): этот метод загружает двоичный тип указанного имени (включая имя пакета), а параметр разрешения указывает, нужно ли одновременно разрешить загрузку.
  • findClass(): логика загрузки пользовательского класса написана в методе findClass().
  • defineClass(): используется для анализа потока байтов в объект класса, который может распознать JVM (логика этого метода была реализована в ClassLoader)
  • resolveClass(): этот метод может быть создан и разрешен с использованием объекта Class класса.

Сначала взгляните на следующий метод loadClass(), из приведенного выше кода видно, что логика не сложная: сначала проверьте, загрузился ли он, если нет, то вызовите loadClass() родительского загрузчика, если родительский загрузчик пуст, разрешите загрузку класса запуска Загрузчик является родительским загрузчиком.Если родительский класс не загружается, будет выдано исключение, а затем будет вызван его собственный метод findClass().

После JDK1.2, если мы настроим загрузчик классов, мы больше не будем переписывать loadClass(), потому что ClassLoader уже реализует loadClass() и использует его для достижения эффекта родительского делегирования. То, что нужно переписать нашему пользовательскому загрузчику классов, это findClass(), зная, что метод findClass() вызывается в методе loadClass(), и когда родительский загрузчик не загружается в методе loadClass(), он вызывает свой собственный findClass () для завершения загрузки класса, чтобы убедиться, что пользовательский загрузчик классов также соответствует родительскому режиму делегирования.

Нарушение модели родительского делегирования

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

первый раз:

Поскольку модель родительского делегирования появилась в JDK1.2, до JDK1.2, родительского делегирования не было.Для прямой совместимости java.lang.ClassLoader после JDK1.2 добавил новый защищенный метод findClass(), единственная логика этого метода заключается в вызове собственного loadClass(). Когда мы анализировали реализацию приведенного выше кода, мы знали, что родительская модель делегирования была реализована в соответствии с loadClass(), поэтому, чтобы использовать родительскую модель делегирования, мы должны поместить свою собственную Логика загрузки класса написана в findClass().

второй раз:

У нас есть некоторые функции, которые Java предоставляет интерфейсы, в то время как другие компании предоставляют классы реализации, такие как наш JDBC, JNDI (несколько компаний предоставляют свои собственные реализации), поэтому SPI (интерфейсы поставщиков услуг), такие как JDBC, JNDI, нуждаются в сторонней реализации, интерфейсы этих SPI принадлежат основной библиотеке и загружаются загрузчиком классов Bootstrap, так как же загрузить классы реализации, предоставляемые этими компаниями? это нашзагрузчик класса контекста потока, на следующем рисунке показан общий рабочий процесс

image
Здесь загрузчик контекста потока по умолчанию использует загрузчик родительского класса, которым является ApplicationClassLoader.

в третий раз:

В третий раз нарушение модели делегированного родителя связано с преследованием пользователя.динамизмВ результате «динамический» относится к кодуГорячая замена, горячее развертывание модуляПодождите, то есть я надеюсь, что программа сможет обновить class-файл без перезапуска.Наиболее типичные примеры — горячее развертывание SpringBoot и OSGi. Возьмем OSGi в качестве примера. Ключом к реализации OSGi модульного горячего развертывания является реализация собственного механизма загрузки классов. Каждый программный модуль (называемый Bundle в OSGi) имеет свой собственный загрузчик классов. Когда Bundle необходимо заменить, он Поменяйте пакет вместе с загрузчиком классов, чтобы обеспечить горячее развертывание.

Поэтому в среде OSGi загрузчик классов представляет собой уже не иерархическую модель, а модель сетки, как показано на рисунке

image

Когда OSGi получает загрузку класса, он выполняет поиск в следующем порядке:

  • Делегировать классы, начинающиеся с java.*, в загрузчик родительских классов для загрузки
  • В противном случае делегируйте классы в списке делегатов загрузчику родительских классов для загрузки.
  • В противном случае делегируйте классы в списке импорта загрузчику классов пакета, который экспортирует этот класс.
  • В противном случае найдите ClassPath текущего пакета и используйте его собственный загрузчик классов для загрузки.
  • Проверьте, может ли пакет фрагментов быть загружен
  • Найдите пакет для списка динамического импорта
  • Если ни один из вышеперечисленных классов не загружен, загрузка не удалась.

Первые два пункта выше по-прежнему соответствуют правилам родительского делегирования, а остальные — поиск загрузчика однорангового класса.

Шаблон загрузчика классов Tomcat

В предыдущей статье мы узнали о работе загрузчиков классов в Java, но основные веб-серверы будут иметь свой собственный набор загрузчиков классов.Почему? Потому что для сервера ему приходится самому решать некоторые проблемы:

  • Библиотеки классов Java, используемые двумя веб-приложениями, развернутыми в одном веб-контейнере, могут быть изолированы друг от друга.. Два разных приложения могут зависеть от разных версий одной и той же сторонней библиотеки классов. Не обязательно, чтобы библиотека классов имела только одну копию на сервере. Сервер должен гарантировать, что библиотеки классов двух приложений можно использовать независимо друг от друга. друг друга.
  • Одна и та же версия одной и той же библиотеки классов, используемая двумя веб-приложениями, развернутыми в одном и том же веб-контейнере, может использоваться совместно друг с другом.. Например, у пользователя может быть 10 приложений, организованных Spring, развернутых на одном сервере.Если 10 копий Spring хранятся в изолированных каталогах каждого приложения, это будет большой тратой ресурсов——В основном это не проблема пустой траты места на диске, а относится к тому факту, что библиотека классов должна быть загружена в память веб-контейнера, когда он используется.Если библиотека классов не может быть разделена, область методов виртуальная машина будет подвержена риску чрезмерного расширения..
  • Веб-контейнер должен быть максимально защищен от развернутого веб-приложения.. Веб-контейнер также реализован на Java, поэтому библиотеку классов веб-контейнера не следует путать с библиотекой классов программы.
  • Веб-контейнер, поддерживающий jsp, должен поддерживать горячее развертывание.. Мы знаем, что при запуске jsp он фактически сначала преобразует jsp в сервлет, затем компилирует его в .class, запускает на виртуальной машине, а затем возвращает клиенту. Когда мы пишем jsp, когда работает сервер tomcat, нам не нужно перезапускать сервер, когда мы модифицируем код прямо в jsp, что позволяет добиться эффекта динамической загрузки классов.

Очевидно, что если Tomcat использует механизм загрузки классов по умолчанию, он не может соответствовать вышеуказанным требованиям.

  1. Невозможно загрузить две разные версии одной и той же библиотеки классов, потому что при загрузке класса по умолчанию важно только имя класса разрешений, первая не работает.
  2. может быть реализован
  3. Загрузка класса по умолчанию заботится только о классе разрешений, поэтому третье правило не работает.
  4. Как мы уже говорили ранее, для JVM потребуется тот же класс и загрузчик классов, чтобы определить, является ли это одним и тем же объектом класса.По умолчанию это определенно невозможно, но мы можем подумать об изменении загрузчика классов один раз при изменении кода jsp.

Далее давайте посмотрим на загрузчик классов Tomcat:

image
++Один WebAppClassLoader может также соответствовать нескольким JspClassLoader++

Давайте поговорим о структуре каталогов Tomcat:

  • В каталоге /common: Библиотека классов может использоваться Tomcat и всеми веб-приложениями.
  • В каталоге /server: библиотека классов может использоваться Tomcat и невидима для всех веб-приложений.
  • В каталоге /shared: библиотека классов может использоваться всеми веб-приложениями, но не видна самому Tomcat.
  • Каталог /WebApp/WEB-INF: библиотека классов может использоваться только этим веб-приложением, она не видна для Tomcat и других веб-приложений.

Давайте посмотрим на процесс загрузки каждого загрузчика классов:

Классы, которые может загружать CommonClassLoader, могут использоваться Catalina ClassLoader и SharedClassLoader, в то время как классы, которые CatalinaClassLoader и Shared ClassLoader могут загружать сами, изолированы друг от друга. WebAppClassLoader может использовать классы, загруженные SharedClassLoader, но отдельные экземпляры WebAppClassLoader изолированы друг от друга. Объем загрузки JasperLoader — это только файл .Class, скомпилированный этим файлом JSP, и его цель — отказаться от него: когда веб-контейнер обнаружит, что файл JSP был изменен, он заменит текущий экземпляр JasperLoader и создаст новый загрузчик классов Jsp для реализации функции HotSwap файла JSP.

Tomcat 6.x по умолчанию объединяет три каталога /common, /server и /shared в один каталог /lib.Библиотека классов в этом каталоге эквивалентна роли библиотеки классов в предыдущем каталоге /common.

Теперь давайте посмотрим, как Tomcat решает предыдущие четыре проблемы:

  • Библиотеки классов Java, используемые двумя веб-приложениями, развернутыми в одном веб-контейнере, могут быть изолированы друг от друга.: изолировать каждый экземпляр WebAppClassLoader друг от друга.
  • Одна и та же версия одной и той же библиотеки классов, используемая двумя веб-приложениями, развернутыми в одном и том же веб-контейнере, может использоваться совместно друг с другом.: может быть помещен в общий или общий каталог для совместного использования этими программами.
  • Веб-контейнер должен быть максимально защищен от развернутого веб-приложения.: CatalinaClassLoader загружает библиотеку классов, требуемую веб-сервером, WebAppClassLoader может получить только библиотеку классов SharedClassLoader.
  • Веб-контейнер, поддерживающий jsp, должен поддерживать горячее развертывание.: обновлять JasperClassLoader при каждом изменении jsp
В: Как упоминалось ранее, если на нашем сервере есть десять программ, организованных Spring, мы можем поместить Spring в каталог Common или Shared для совместного использования, но Spring должен иметь доступ к пользовательской программе для управления классом, то есть доступ не в пределах его объем загрузки пользовательской программы, как этого добиться?

Ранее мы говорили об уничтожении модели делегирования. Вот пример. Вы можете использовать загрузчик контекста потока, чтобы позволить загрузчику родительского класса запросить загрузчик дочернего класса для выполнения роли загрузки класса.

Анализ распространенных ошибок исключений класса загрузки

ClassNotFoundException

Эта ошибка означает, что когда JVM загружает байт-код указанного файла в память, соответствующий байт-код не может быть найден. Решение состоит в том, чтобы выяснить, есть ли указанный файл в текущем каталоге пути к классам (this.getClass().getClassLoader().getResource("").toString() может просматривать текущий путь к классам)

NoClassDefFoundError

Такая ошибка возникает, когда используется новое ключевое слово, атрибут ссылается на класс, наследует интерфейс или реализует класс или параметр метода ссылается на класс.В это время виртуальная машина неявно загружает эти классы и находит их Исключение класса не существует. Решение этой ошибки состоит в том, чтобы убедиться, что классы, на которые ссылается каждый класс, находятся в текущем пути к классам.

UnsatisfiedLinkError

Возможно, библиотека в JVM была случайно удалена при запуске JVM.

ClassCastException

Невозможно преобразовать, это может быть очень распространенным явлением для новичков (таких как я, ха-ха), решение состоит в том, чтобы использовать instanceof, чтобы проверить, является ли целевой тип целевым типом перед преобразованием.

ExceptionInInitializerError

Это исключение вызвано сбоем процесса инициализации статического блока во время загрузки класса. Так как он фигурирует в основном потоке, отвечающем за запуск программы, лучше начинать анализ с основного класса, который вы указали в аргументах командной строки, либо объявленный вами как public static void main(String args[ ]) метод этого класса. Это исключение, скорее всего, будет сопровождаться NoClassDefFoundError, поэтому, когда возникает NoClassDefFoundError, мы сначала смотрим, возникает ли ExceptionInInitializerError.

пользовательский загрузчик классов

Далее нам нужно самим написать загрузчик классов.Прежде чем мы начнем писать, нам нужно знать, зачем нам нужно писать собственный загрузчик классов?

  • Нужные нам классы не обязательно хранятся в уже установленном classPath (есть путь, загружаемый системным загрузчиком классов AppClassLoader), для загрузки файлов классов в кастомном пути нам нужен свой собственный ClassLoader
  • Иногда мы не обязательно читаем классы из файлов классов, но можем читать классы из входного потока сети, что требует некоторых операций шифрования и дешифрования, что требует реализации логики загрузки классов самостоятельно, ну и конечно другой специальной обработки. применяется.
  • Вы можете определить механизм реализации класса и реализовать горячее развертывание класса.Например, модуль пакета в OSGi реализуется путем реализации собственного ClassLoader.

Приступим к настройке загрузчика классов

Загрузка пользовательского класса файлов

package SelfClassLoader;

import java.io.*;

public class FileClassLoader extends ClassLoader {
    private String rootDir;

    public FileClassLoader(String rootDir){
        this.rootDir = rootDir;
    }

    /**
     * 编写findClass方法的逻辑
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //获取类的class文件字节数组
        byte[] classData = getClassData(name);
        if (classData == null){
            throw new ClassNotFoundException();
        } else {
            //直接生成class对象
            return defineClass(name,classData,0,classData.length);
        }
    }


    /**
     * 编写获取class文件并转换为字节码流的逻辑
     * @param className
     * @return
     */
    private byte[] getClassData(String className){
        //读取类文件的字节
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int bytesNumRead = 0;
            // 读取类文件的字节码
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 类文件的完整路径
     * @param className
     * @return
     */
    private String classNameToPath(String className) {
        return rootDir + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
    }

    /**
     * 读取文件
     */
    public static void main(String[] args) throws ClassNotFoundException {
        String rootDir="C:\\java\\JVM\\JVMInstruction\\src";
        //创建自定义文件类加载器
        FileClassLoader loader = new FileClassLoader(rootDir);

        try {
            //加载指定的class文件,加上包名
            Class<?> object1=loader.loadClass("SelfClassLoader.DemoObj");
            System.out.println(object1.newInstance().toString());

            //输出结果:I am DemoObj
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

Мы находим файл класса с помощью метода getClassData() и преобразуем его в поток байтов, а также переписываем метод findClass() для создания объекта класса класса с помощью метода defineClass(). В основном методе метод loadClass() вызывается для загрузки файла класса по указанному пути.Поскольку загрузчик запускаемого класса, загрузчик класса расширения и загрузчик системного класса не могут найти класс в своем пути, в конечном итоге будет создан пользовательский класс Загрузка загрузчиком, то есть вызов метода findClass() для загрузки.

Другой способ — наследовать класс URLClassLoader, а затем установить URL-адрес пользовательского пути для загрузки класса по URL-адресу, что встречается чаще.

package SelfClassLoader;

import java.io.File;
import java.net.*;

public class PathClassLoader extends URLClassLoader {

    private String packageName = "net.lijunfeng.classloader";

    public PathClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public PathClassLoader(URL[] urls) {
        super(urls);
    }

    public PathClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        super(urls, parent, factory);
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException{
        Class<?> aClass = findLoadedClass(name);
        if (aClass != null){
            return aClass;
        }
        if (!packageName.startsWith(name)){
            return super.loadClass(name);
        } else {
            return findClass(name);
        }
    }

    public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
        String rootDir="C:\\java\\JVM\\JVMInstruction\\src";
        //创建自定义文件类加载器
        File file = new File(rootDir);
        //File to URI
        URI uri=file.toURI();
        URL[] urls={uri.toURL()};

        PathClassLoader loader = new PathClassLoader(urls);

        try {
            //加载指定的class文件
            Class<?> object1=loader.loadClass("SelfClassLoader.DemoObj");
            System.out.println(object1.newInstance().toString());

            //输出结果:I am DemoObj
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Суммировать

  • Мы знаем, что роль загрузчика классов заключается в хранении файла .class в памяти (два типа)
  • Знайте его конкретные шаги и функцию каждого шага
  • Понять три загрузчика классов Java и модель родительского делегирования и разрушить родительскую модель делегирования.
  • Понять проблемы, которые необходимо решить механизму загрузки классов Tomcat, и способы их решения.
  • Общие сведения об исключениях загрузки классов и общих решениях
  • А также знать примерную кодовую реализацию механизма загрузки классов
  • Наконец мы написали собственный загрузчик классов
.