Рубрики
Без рубрики

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

Загрузчик классов Java используется для загрузки классов в память JVM. В Java существует три типа встроенных загрузчиков классов. Мы также можем создавать пользовательские загрузчики классов.

Автор оригинала: Pankaj Kumar.

Загрузчик классов Java является одним из важнейших, но редко используемых компонентов при разработке проектов. Я никогда не расширял загрузчик классов ни в одном из своих проектов. Но идея иметь свой собственный загрузчик классов, который может настраивать загрузку классов Java, является захватывающей.

В этой статье будет представлен обзор загрузчика классов Java, а затем перейдем к созданию пользовательского загрузчика классов в Java.

Что такое загрузчик классов Java?

Мы знаем, что программа Java работает на Виртуальной машине Java (JVM). Когда мы компилируем класс Java, JVM создает байт-код, который не зависит от платформы и машины. Байт-код хранится в файле .class . Когда мы пытаемся использовать класс, загрузчик классов загружает его в память.

Встроенные типы загрузчиков классов

В Java существует три типа встроенного загрузчика классов.

  1. Загрузчик классов начальной загрузки – Он загружает внутренние классы JDK. Он загружается rt.jar и другие основные классы, например java.lang.* классы пакетов.
  2. Загрузчик классов расширений – Он загружает классы из каталога расширений JDK, обычно каталога $JAVA_HOME/lib/ext.
  3. Загрузчик системных классов – Этот загрузчик классов загружает классы из текущего пути к классам. Мы можем задать путь к классу при вызове программы, используя параметр командной строки-cp или-classpath.

Иерархия загрузчика классов

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

Давайте разберемся в этом, выполнив приведенную ниже программу java.

package com.journaldev.classloader;

public class ClassLoaderTest {

    public static void main(String[] args) {

        System.out.println("class loader for HashMap: "
                + java.util.HashMap.class.getClassLoader());
        System.out.println("class loader for DNSNameService: "
                + sun.net.spi.nameservice.dns.DNSNameService.class
                        .getClassLoader());
        System.out.println("class loader for this class: "
                + ClassLoaderTest.class.getClassLoader());

        System.out.println(com.mysql.jdbc.Blob.class.getClassLoader());

    }

}

Выход:

class loader for HashMap: null
class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@7c354093
class loader for this class: sun.misc.Launcher$AppClassLoader@64cbbe37
sun.misc.Launcher$AppClassLoader@64cbbe37

Как Работает Загрузчик Классов Java?

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

  • Файл java.util.Загрузчик классов HashMap имеет значение null, что отражает загрузчик классов начальной загрузки. Загрузчиком класса DNSNameService является ExtClassLoader. Поскольку сам класс находится в ПУТИ к классу, системный загрузчик классов загружает его.
  • Когда мы пытаемся загрузить HashMap, наш системный загрузчик классов делегирует его загрузчику классов расширения. Загрузчик классов расширения делегирует его загрузчику классов начальной загрузки. Загрузчик загрузочного класса находит класс HashMap и загружает его в память JVM.
  • Тот же процесс выполняется для класса DNSNameService. Но загрузчик загрузочных классов не может найти его, так как он находится в $JAVA_HOME/lib/ext/dnsns.jar . Следовательно, он загружается загрузчиком классов расширений.
  • Класс Blob-объектов включен в jar-файл MySQL JDBC Connector (mysql-connector-java-5.0.7-bin.jar), который присутствует в пути сборки проекта. Он также загружается системным загрузчиком классов.
  • Классы, загруженные загрузчиком дочерних классов, имеют видимость классов, загруженных загрузчиками родительских классов. Таким образом, классы, загружаемые Системным загрузчиком классов, имеют видимость классов, загружаемых расширениями и загрузчиком загрузчиков.
  • Если есть однояйцевые загрузчики классов, то они не могут получить доступ к классам, загруженным друг другом.

Зачем писать пользовательский загрузчик классов на Java?

Загрузчик классов Java по умолчанию может загружать классы из локальной файловой системы, что достаточно хорошо для большинства случаев. Но, если вы ожидаете класс во время выполнения или с FTP-сервера или через стороннюю веб-службу во время загрузки класса, вам необходимо расширить существующий загрузчик классов. Например, Appletviewer загружает классы с удаленного веб-сервера.

Методы загрузчика классов Java

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

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

Мы создадим наш собственный загрузчик классов, расширив класс загрузчика классов и переопределив метод loadClass(строковое имя).

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

1. CCLoader.java

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

  1. частный байт[] loadclassdata(строковое имя) : Этот метод будет считывать файл класса из файловой системы в массив байтов.
  2. частный класс getClass(строковое имя) : Этот метод вызовет функцию loadClassFileData() и, вызвав родительский метод defineClass (), создаст класс и вернет его.
  3. общедоступный класс loadClass(строковое имя) : Этот метод отвечает за загрузку класса. Если имя класса начинается с com.journaldev (наши примеры классов), то он загрузит его с помощью метода getClass() или вызовет родительскую функцию loadClass() для его загрузки.
  4. общедоступный загрузчик классов(родительский загрузчик классов) : Это конструктор, который отвечает за настройку родительского загрузчика классов.
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
 
/**
 * Our Custom ClassLoader to load the classes. Any class in the com.journaldev
 * package will be loaded using this ClassLoader. For other classes, it will delegate the request to its Parent ClassLoader.
 *
 */
public class CCLoader extends ClassLoader {
 
    /**
     * This constructor is used to set the parent ClassLoader
     */
    public CCLoader(ClassLoader parent) {
        super(parent);
    }
 
    /**
     * Loads the class from the file system. The class file should be located in
     * the file system. The name should be relative to get the file location
     *
     * @param name
     *            Fully Classified name of the class, for example, com.journaldev.Foo
     */
    private Class getClass(String name) throws ClassNotFoundException {
        String file = name.replace('.', File.separatorChar) + ".class";
        byte[] b = null;
        try {
            // This loads the byte code data from the file
            b = loadClassFileData(file);
            // defineClass is inherited from the ClassLoader class
            // that converts byte array into a Class. defineClass is Final
            // so we cannot override it
            Class c = defineClass(name, b, 0, b.length);
            resolveClass(c);
            return c;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
 
    /**
     * Every request for a class passes through this method. If the class is in
     * com.journaldev package, we will use this classloader or else delegate the
     * request to parent classloader.
     *
     *
     * @param name
     *            Full class name
     */
    @Override
    public Class loadClass(String name) throws ClassNotFoundException {
        System.out.println("Loading Class '" + name + "'");
        if (name.startsWith("com.journaldev")) {
            System.out.println("Loading Class using CCLoader");
            return getClass(name);
        }
        return super.loadClass(name);
    }
 
    /**
     * Reads the file (.class) into a byte array. The file should be
     * accessible as a resource and make sure that it's not in Classpath to avoid
     * any confusion.
     *
     * @param name
     *            Filename
     * @return Byte array read from the file
     * @throws IOException
     *             if an exception comes in reading the file
     */
    private byte[] loadClassFileData(String name) throws IOException {
        InputStream stream = getClass().getClassLoader().getResourceAsStream(
                name);
        int size = stream.available();
        byte buff[] = new byte[size];
        DataInputStream in = new DataInputStream(stream);
        in.readFully(buff);
        in.close();
        return buff;
    }
}

2. CCRun.java

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

После загрузки класса мы используем API отражения Java для вызова его методов.

import java.lang.reflect.Method;
 
public class CCRun {
 
    public static void main(String args[]) throws Exception {
        String progClass = args[0];
        String progArgs[] = new String[args.length - 1];
        System.arraycopy(args, 1, progArgs, 0, progArgs.length);

        CCLoader ccl = new CCLoader(CCRun.class.getClassLoader());
        Class clas = ccl.loadClass(progClass);
        Class mainArgType[] = { (new String[0]).getClass() };
        Method main = clas.getMethod("main", mainArgType);
        Object argsArray[] = { progArgs };
        main.invoke(null, argsArray);

        // Below method is used to check that the Foo is getting loaded
        // by our custom class loader i.e CCLoader
        Method printCL = clas.getMethod("printCL", null);
        printCL.invoke(null, new Object[0]);
    }
 
}

3. Foo.java и Bar.java

Это наши тестовые классы, которые загружаются нашим пользовательским загрузчиком классов. У них есть метод print CL () , который вызывается для печати информации о загрузчике классов.

Класс Foo будет загружен нашим пользовательским загрузчиком классов. Foo использует класс Bar, поэтому класс Bar также будет загружен нашим пользовательским загрузчиком классов.

package com.journaldev.cl;
 
public class Foo {
    static public void main(String args[]) throws Exception {
        System.out.println("Foo Constructor >>> " + args[0] + " " + args[1]);
        Bar bar = new Bar(args[0], args[1]);
        bar.printCL();
    }
 
    public static void printCL() {
        System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader());
    }
}
package com.journaldev.cl;
 
public class Bar {
 
    public Bar(String a, String b) {
        System.out.println("Bar Constructor >>> " + a + " " + b);
    }
 
    public void printCL() {
        System.out.println("Bar ClassLoader: "+Bar.class.getClassLoader());
    }
}

4. Шаги выполнения пользовательского загрузчика классов Java

Прежде всего, мы скомпилируем все классы через командную строку. После этого мы запустим класс CCRun, передав три аргумента. Первый аргумент-это полностью классифицированное имя класса Foo, которое будет загружено вашим загрузчиком классов. Два других аргумента передаются вместе с основной функцией класса Foo и конструктором панели. Шаги выполнения и выходные данные будут такими, как показано ниже.

$ javac -cp . com/journaldev/cl/Foo.java
$ javac -cp . com/journaldev/cl/Bar.java
$ javac CCLoader.java
$ javac CCRun.java
CCRun.java:18: warning: non-varargs call of varargs method with inexact argument type for last parameter;
cast to java.lang.Class for a varargs call
cast to java.lang.Class[] for a non-varargs call and to suppress this warning
Method printCL = clas.getMethod("printCL", null);
^
1 warning
$ java CCRun com.journaldev.cl.Foo 1212 1313
Loading Class 'com.journaldev.cl.Foo'
Loading Class using CCLoader
Loading Class 'java.lang.Object'
Loading Class 'java.lang.String'
Loading Class 'java.lang.Exception'
Loading Class 'java.lang.System'
Loading Class 'java.lang.StringBuilder'
Loading Class 'java.io.PrintStream'
Foo Constructor >>> 1212 1313
Loading Class 'com.journaldev.cl.Bar'
Loading Class using CCLoader
Bar Constructor >>> 1212 1313
Loading Class 'java.lang.Class'
Bar ClassLoader: CCLoader@71f6f0bf
Foo ClassLoader: CCLoader@71f6f0bf
$

Если вы посмотрите на вывод, он пытается загрузить com.journaldev.cl.Foo класс. Так как это расширение java.lang.Класс объекта, он пытается сначала загрузить класс объекта.

Таким образом, запрос поступает в метод CCLoader loadClass, который делегирует его родительскому классу. Таким образом, загрузчики родительских классов загружают объекты, строки и другие классы java.

Наш загрузчик классов загружает только классы Foo и Bar из файловой системы. Это ясно из вывода функции println ().

Мы можем изменить функцию загрузки данных файла класса() для чтения массива байтов с FTP-сервера или путем вызова любой сторонней службы, чтобы получить массив байтов класса на лету.

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

Создание пользовательского загрузчика классов в качестве загрузчика классов по умолчанию

Мы можем сделать ваш пользовательский загрузчик классов загрузчиком по умолчанию при запуске JVM с помощью параметров Java.

Например, я снова запущу программу ClassLoaderTest после предоставления опции загрузчика классов java.

$ javac -cp .:../lib/mysql-connector-java-5.0.7-bin.jar com/journaldev/classloader/ClassLoaderTest.java
$ java -cp .:../lib/mysql-connector-java-5.0.7-bin.jar -Djava.system.class.loader=CCLoader com.journaldev.classloader.ClassLoaderTest
Loading Class 'com.journaldev.classloader.ClassLoaderTest'
Loading Class using CCLoader
Loading Class 'java.lang.Object'
Loading Class 'java.lang.String'
Loading Class 'java.lang.System'
Loading Class 'java.lang.StringBuilder'
Loading Class 'java.util.HashMap'
Loading Class 'java.lang.Class'
Loading Class 'java.io.PrintStream'
class loader for HashMap: null
Loading Class 'sun.net.spi.nameservice.dns.DNSNameService'
class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@24480457
class loader for this class: CCLoader@38503429
Loading Class 'com.mysql.jdbc.Blob'
sun.misc.Launcher$AppClassLoader@2f94ca6c
$

Загрузчик CCLoader загружает класс ClassLoaderTest, потому что он находится в com.journaldev пакете.