Интервьюер: расскажите о своем понимании отражения в Java.

Java
Интервьюер: расскажите о своем понимании отражения в Java.

Механизм отражения, предоставляемый Java, позволяет вам проверять информацию о классе во время выполнения.

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

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

Загрузка класса относится к.classДвоичные данные в файле считываются в内存, поместите его в JVM方法区внутрь, затем вплощадь для созданияjava.lang.ClassОбъект, используемый для инкапсуляции структуры данных класса в области методов. Конечный продукт загрузки класса находится в области кучиClass对象, объект Class инкапсулирует структуру данных класса в области методов и предоставляет программистам Java интерфейс для доступа к структуре данных в области методов.


Все типы в Java, включая примитивные типы (int, long, floatд.), даже массивы имеют связанные с ними объекты класса Class.

Объекты класса автоматически генерируются JVM.Каждый раз, когда класс загружается, JVM автоматически генерирует для него объект класса.

Объект класса

экземпляр.getClass()

по объектуgetClass()получить каждый实例Соответствующий объект класса

String name = "hello";
Class stringClass = name.getClass();
System.out.println("类的名称:" + stringClass.getName());
System.out.println("是否为接口:" + stringClass.isInterface());
System.out.println("是否为基本类型:" + stringClass.isPrimitive());
System.out.println("是否为数组:" + stringClass.isArray());
System.out.println("父类名称:" + stringClass.getSuperclass().getName());
类的名称:java.lang.String
是否为接口:false
是否为基本类型:false
是否为数组:false
父类名称:java.lang.Object

имя класса.класс

Вы также можете напрямую использовать следующий метод, чтобы получить объект класса класса String.

Class stringClass = String.class;

Class.forName()

В некоторых приложениях нельзя заранее знать, какой класс загрузит пользователь, можно использовать статический метод классаforName()для динамической загрузки категорий

Class c = Class.forName(args[0]);
System.out.println("类的名称:" + c.getName());
System.out.println("是否为接口:" + c.isInterface());
System.out.println("是否为基本类型:" + c.isPrimitive());
System.out.println("是否为数组:" + c.isArray());
System.out.println("父类名称:" + c.getSuperclass().getName());
$ java Demo1 java.util.Scanner  
类的名称:java.util.Scanner
是否为接口:false
是否为基本类型:false
是否为数组:false
父类名称:java.lang.Object

Существует две версии Class.forName(), в приведенной выше версии указывается только полное имя класса, а другая версия позволяет указать имя класса, выполнять ли блок статического кода при загрузке и выполнять загрузчик класса (ClassLoader)

static Class forName(String name, boolean initialize, ClassLoader loader)
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// Class.forName() 加载类 默认会执行初始化块
Class.forName("Test2");
// Class.forName() 加载类 第二个参数 可以控制是否执行初始化块
Class.forName("Test2", false, loader);

class Test2 {
    static {
        System.out.println("静态初始化块执行了!");
    }
}

Получить информацию от объекта класса

Объект класса представляет загруженную категорию.После получения объекта класса вы можете получить информацию, относящуюся к категории.package, constructor, field, methodи другая информация.
Каждый тип информации имеет соответствующую категорию

  • package: java.lang.reflect.Package
  • constructor: java.lang.reflect.Constructor
  • field: java.lang.reflect.Field
  • method: java.lang.reflect.Method
Class c = Class.forName(args[0]);
System.out.println("包信息package:" + c.getPackage());
System.out.println("类修饰符modifier:" + c.getModifiers());
System.out.println("构造方法constructor:");
Arrays.stream(c.getDeclaredConstructors()).forEach(System.out::println);
System.out.println("成员变量fields:");
Arrays.stream(c.getDeclaredFields()).forEach(System.out::println);
$ java Demo1 java.util.ArrayList
包信息package:package java.util
类修饰符modifier:1
构造方法constructor:
public java.util.ArrayList(java.util.Collection)
public java.util.ArrayList()
public java.util.ArrayList(int)
成员变量fields:
private static final long java.util.ArrayList.serialVersionUID
private static final int java.util.ArrayList.DEFAULT_CAPACITY
private static final java.lang.Object[] java.util.ArrayList.EMPTY_ELEMENTDATA
private static final java.lang.Object[] java.util.ArrayList.DEFAULTCAPACITY_EMPTY_ELEMENTDATA
transient java.lang.Object[] java.util.ArrayList.elementData
private int java.util.ArrayList.size
private static final int java.util.ArrayList.MAX_ARRAY_SIZE

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

Java загружает классы только тогда, когда они нужны.Class Loaderбыть реализованным.

когда вы пытаетесь выполнитьjava xxxкоманда, java попытается найтиJREкаталог установки, затем найдите
jvm.dll, затем запустите JVM и выполните операции инициализации, а затем сгенерируйте
BootstrapLoader,Bootstrap Loaderбудет загружатьсяExtended Loader, и установитеExtended LoaderРодительBootstrapLoader, тогдаBootstrap Loaderбудет загружатьсяApplication Loader, и воляApplication LoaderРодитель настроен на
Extended Loader

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

BootstrapLoaderпоискsun.boot.library.pathкласс, указанный в , вы можете использовать
System.getProperty("sun.boot.library.path")получить

/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib

загрузчик класса расширения

Extended Loader(sun.misc.Launcher$ExtClassLoader) написан на Java и ищет системные параметрыjava.ext.dirsкатегория, указанная вSystem.getProperty("java.ext.dirs")получить

/Users/dsying/Library/Java/Extensions:
/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:
/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:/usr/lib/java

загрузчик класса приложения

Application Loader
(sun.misc.Launcher$AppClassLoader) написан на Java и ищет системные параметрыjava.class.pathкатегория, указанная вSystem.getProperty("java.class.path")получить, используяjava xxxвыполнение команды.classфайл байт-кода, вы можете передать-cpУстановка параметровclasspath

java –cp ./classes SomeClass

Связь между загрузчиками классов

ClassLoader loader Thread.currentThread().getContextClassLoader();
// sun.misc.Launcher$AppClassLoader@18b4aac2   应用类加载器
System.out.println(loader);
// sun.misc.Launcher$ExtClassLoader@610455d6   扩展类加载器
System.out.println(loader.getParent());
// Bootstrap ClassLoader 启动类加载器(用C语言实现,所以此处返回null)
System.out.println(loader.getParent().getParent());
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@610455d6
null

Существует три способа загрузки класса:

  1. Когда командная строка запускает приложение, оно инициализируется и загружается JVM.
  2. пройти черезClass.forName()Метод динамической загрузки
  3. пройти черезClassLoader.loadClass()Метод динамической загрузки
Разница между Class.forName() и ClassLoader.loadClass()
  • Class.forName(): Помимо загрузки файла .class класса в jvm, он также будет интерпретировать класс и выполнять статический блок в классе;
  • ClassLoader.loadClass(): Только сделать одно, то есть загрузить файл .class в jvm, содержимое в static не будет выполняться, а статический блок будет выполняться только в newInstance.
  • Class.forName(name, initialize, loader)Функции с параметрами также могут управлять загрузкой статических блоков. И только метод newInstance() вызывается вызовом конструктора для создания объекта класса.

Механизм загрузки классов JVM

  • 全盘负责, когда загрузчик классов отвечает за загрузку класса, другие классы, от которых зависит класс, и ссылки также будут загружены загрузчиком классов, если для загрузки не отображается другой загрузчик классов.
  • 父类委托, пусть загрузчик родительского класса попытается сначала загрузить класс и попытается загрузить класс только из своего собственного пути к классам, если загрузчик родительского класса не может загрузить класс
  • 缓存机制, механизм кэширования гарантирует, что все загруженные классы будут кэшированы.Когда класс необходимо использовать в программе, загрузчик классов сначала будет искать класс в области кэша, и система будет считывать соответствующий класс, только если области кэша не существует.Двоичные данные преобразуются в объект класса и сохраняются в буфере. Вот почему после изменения класса необходимо перезапустить JVM, чтобы изменение программы вступило в силу.

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

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

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

  1. когдаAppClassLoaderПри загрузке класса он не пытается сначала загрузить сам класс, а делегирует запрос на загрузку класса загрузчику родительского класса.ExtClassLoaderзаканчивать.
  2. когдаExtClassLoaderПри загрузке класса он не пытается загрузить сам класс в первую очередь, а делегирует запрос на загрузку классаBootStrapClassLoaderзаканчивать.
  3. еслиBootStrapClassLoaderЗагрузка не удалась (например, в$JAVA_HOME/jre/libкласс не найден), будем использоватьExtClassLoaderпопытаться загрузить;
  4. какExtClassLoaderтакже не загружается, он будет использоватьAppClassLoaderзагрузить, еслиAppClassLoaderтакже не загружается, будет сообщено об исключенииClassNotFoundException.

Анализ исходного кода ClassLoader:

public Class<?> loadClass(String name)throws ClassNotFoundException {
    return loadClass(name, false);
}
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 {
                //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
                //如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                c = findClass(name);
            }
        }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

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

  • Системный класс предотвращает появление в памяти нескольких копий одного и того же байт-кода.
  • Обеспечение безопасной и стабильной работы Java-программ

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

Пользовательские загрузчики классов обычно наследуются отClassLoaderкласс, обращенный сверхуloadClassметод для анализа, нам нужно только переписатьfindClassметод. Ниже мы используем пример для демонстрации процесса настройки загрузчика классов:

package com.github.hcsp.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    // 存放字节码文件的目录
    private final File bytecodeFileDirectory;

    public MyClassLoader(File bytecodeFileDirectory) {
        this.bytecodeFileDirectory = bytecodeFileDirectory;
    }

    // 还记得类加载器是做什么的么?
    // "从外部系统中,加载一个类的定义(即Class对象)"
    // 请实现一个自定义的类加载器,将当前目录中的字节码文件加载成为Class对象
    // 提示,一般来说,要实现自定义的类加载器,你需要覆盖以下方法,完成:
    //
    // 1.如果类名对应的字节码文件存在,则将它读取成为字节数组
    //   1.1 调用ClassLoader.defineClass()方法将字节数组转化为Class对象
    // 2.如果类名对应的字节码文件不存在,则抛出ClassNotFoundException
    //
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getByteArrayFromFile(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, classData, 0, classData.length);
    }

    byte[] getByteArrayFromFile(String className) throws ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        File file = new File(bytecodeFileDirectory, className + ".class");
        int len = 0;
        try {
            byte[] bufferSize = new byte[1024];
            FileInputStream fis = new FileInputStream(file);
            while ((len = fis.read(bufferSize)) != -1) {
                bos.write(bufferSize, 0, len);
            }
        } catch (FileNotFoundException e) {
            throw new ClassNotFoundException();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bos.toByteArray();
    }

    public static void main(String[] args) throws Exception {
        File projectRoot = new File(System.getProperty("basedir", System.getProperty("user.dir")));
        MyClassLoader myClassLoader = new MyClassLoader(projectRoot);

        Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass");
        Object testClassInstance = testClass.getConstructor().newInstance();
        String message = (String) testClass.getMethod("sayHello").invoke(testClassInstance);
        System.out.println(message);
    }
}

пользовательский загрузчик классовосновнойлежит в对字节码文件的获取, если это зашифрованный байт-код, вам нужно расшифровать файл в этом классе. Поскольку это всего лишь демонстрация, я не шифровал файл класса, поэтому процесса расшифровки нет.

разное

Создание объектов с помощью отражения

Вы можете использовать классnewInstance()метод создания экземпляра

Class c = Class.forName(className);
Object obj = c.newInstance();

метод вызова

Метод в классе можно получить с помощью отражения, а класс, соответствующий методу,
java.lang.reflect.Method, вы можете использовать его
invoke()метод для вызова указанного метода

Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass");
Object testClassInstance = testClass.getConstructor().newInstance();
String message = (String) testClass.getMethod("sayHello").invoke(testClassInstance);