автор:DeppWang,исходный адрес
существуетСоздание колес: реализация простого контейнера Spring IoCВ этой статье упоминается, что Spring использует отражение при создании экземпляров Bean и внедрении зависимостей, В этой статье будет специально проанализировано отражение в Spring и принцип отражения.
1. Отражение весной
1.1 Отражение при создании экземпляра Bean
// 通过类加载器,根据 class 路径,得到其类对象
Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
// 根据类对象生成 Bean 实例
return clz.newInstance();
отражатель сейчасclz.newInstance();
, основной код можно разделить на две части:
1,использовать отражениеПолучить всю информацию о конструкторе (объект Constructor) текущего класса PetStoreService
// java.lang.Class.java
// 调用 native 方法,此时 publicOnly 为 false
res = getDeclaredConstructors0(publicOnly);
// native 方法,从 jvm 中的 class 文件中获取构造方法信息,再转换为 Constructor 对象
private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
2,использовать отражениеГенерировать экземпляр конструктором по умолчанию
// sun.reflect.NativeConstructorAccessorImpl.java
// 调用 native 方法,var1 代表构造方法的参数,此时为 null
return newInstance0(this.c, var1);
// native 方法,真正生成实例的方法,执行 class 文件的构造方法 <init>
private static native Object newInstance0(Constructor<?> var0, Object[] var1);
1.2 Отражение во время внедрения зависимостей конструктора
// 通过反射获取当前类所有的构造方法信息(Constructor 对象)
Constructor<?>[] candidates = beanClass.getDeclaredConstructors();
// 设置构造方法参数实例
Object[] argsToUse = new Object[parameterTypes.length];
argsToUse[i] = getBean(beanNames.get(i));
// 使用带有参数的 Constructor 对象实现实例化 Bean。此时使用反射跟上面一样(newInstance0),只是多了参数
return constructorToUse.newInstance(argsToUse);
1.3 Отражение во время внедрения зависимости метода setter()
// 通过反射获取当前类所有的方法信息(Method 对象)
Method[] methods = bean.getClass().getDeclaredMethods();
// 获得方法参数实例
Object propertyBean = getBean(propertyName);
// 通过反射执行调用 setter() 方法。invoke:调用方法,propertyBean 作为方法的参数
method.invoke(bean, propertyBean);
Основной код в bean.getClass().getDeclaredMethods();:
// java.lang.Class.java
// 调用 native 方法,publicOnly 为 false
getDeclaredMethods0(publicOnly);
// native 方法,从 jvm 中的 class 文件中获取方法信息,再转换为 Method
private native Method[] getDeclaredMethods0(boolean publicOnly);
Основной код в method.invoke(bean, propertyBean);:
// sun.reflect.NativeMethodAccessorImpl.java
// 调用 native 方法,var1: bean、var2: propertyBean
return invoke0(this.method, var1, var2);
// native 方法,运行 class 文件中的字节码指令
private static native Object invoke0(Method var0, Object var1, Object[] var2);
1.4 Отражение во время внедрения зависимости @Autowired
// 通过反射得到当前类所有的字段信息(Field 对象)
Field[] fields = bean.getClass().getDeclaredFields();
// 判断字段是否有 @Autowired 注解
Annotation ann = field.getAnnotation(Autowired.class);
// 设置字段可连接,相当于将非 public(private、default、protect)更改为 public
field.setAccessible(true);
// 通过反射设置字段的值
field.set(bean, getBean(field.getName()));
Основной код в bean.getClass().getDeclaredFields();:
// java.lang.Class.java
// 调用 native 方法,此时 publicOnly 为 false
getDeclaredFields0(publicOnly);
// native 方法,从 jvm 中获取 class 文件的字段信息,再转换为 Field
private native Field[] getDeclaredFields0(boolean publicOnly);
Основной код в field.set(bean, getBean(field.getName())); :
// sun.reflect.UnsafeObjectFieldAccessorImpl.java
// 调用 native 方法,将目标对象 var1 指定偏移量 fieldOffset 处的字段值设置(修改)为 var2。var1 为 bean, var2 为参数实例
unsafe.putObject(var1, this.fieldOffset, var2);
// sun.misc.Unsafe.java
// native 方法,直接修改堆中对象字段的数据
public native void putObject(Object var1, long var2, Object var4);
Во-вторых, файл класса и объект класса
Файл класса скомпилирован из файла java, а файл класса содержит таблицу полей, таблицу методов,<init>
методы (конструкторы) и т.д.
Когда загрузчик классов загружает файл класса в область метаданных виртуальной машины (область методов, jdk1.7), виртуальная машина создает соответствующий объект класса (экземпляр класса). И преобразуйте файл класса из статической структуры, хранящейся на диске, в структуру времени выполнения, хранящуюся в памяти.
Мы можем думать, что класс (файл класса) соответствует объекту класса, и все объекты текущего класса совместно используют объект класса.Объект класса служит точкой входа для доступа к файлу класса, хранящемуся в файле jvm.
package java.lang;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
public final class Class<T> {
private native Field[] getDeclaredFields0(boolean publicOnly);
private native Method[] getDeclaredMethods0(boolean publicOnly);
private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
// ReflectionData 缓存反射对象
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
...
}
}
2.1, способ получить объект класса
// 1、通过对象
Class cls = object.getClass();
// Object.java
public final native Class<?> getClass();
// 2、通过类加载器
Class cls = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
// 3、通过 Class 类,本质上也是通过类加载器
Class cls = Class.forName("org.deppwang.litespring.v1.service.PetStoreService");
// Class.java
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
3. Метод отражения
Ниже приведены широко используемые методы отражения.
3.1. Связанные с полем
Field[] fields = cls.getFields(); // 获取所有公共的 Field(包括父类)
Field[] fields = cls.getDeclaredFields(); // 获取当前类的所有 Field(不包括父类),包括公共和非公共
Field field = cls.getDeclaredField("fieldName"); // 指定获取当前类某个 Field
field.set(Object, Object); // 设置(修改)字段值
field.get(Object); // 获取字段值
Основной код field.get(Object):
// 调用 native 方法,获取字段对应的值
return unsafe.getObject(var1, this.fieldOffset);
// native 方法,从堆中获取对象指定位置的对象
public native Object getObject(Object var1, long var2);
3.2, Метод, связанный с
Method[] methods = cls.getMethods(); // 获取所有公共的 Method(包括父类)
Method[] methods = cls.getDeclaredMethods(); // 获取当前类的所有 Method(不包括父类),包括公共和非公共
method.invoke(Object instance, Object... parameters); // 运行方法
Сценарии использования метода запуска: либо изменить данные объекта, например, метод void setter(), либо получить возвращаемый результат выполнения метода.
String result = method.invoke().toString();
3.3, связанный с конструктором
Constructor<?>[] constructors = cls.getConstructors(); // 获取所有公共的 Constructor(包括父类)
Constructor<?>[] constructors = cls.getDeclaredConstructors(); // 获取当前类的所有Constructor(不包括父类),包括公共和非公共
constructor.newInstance(Object... parameters); // 运行构造方法
Если конструктор явно не написан, компилятор Java создаст для класса конструктор по умолчанию.<init>
В-четвертых, нативный метод
В Java 1.1 добавлен «Java Native Interface» (JNI), JNI — очень инклюзивный программный интерфейс, позволяющий нам вызывать собственные методы из приложений Java, собственные методы из других языков (C, C++ или ассемблера) и т. д.). Нативные методы используются для реализации функций, с которыми Java не может справиться.
4.1 Простой пример
Простой пример использования Java Native Interface (JNI) в Java.
- окрестности:jdk8, макОС 10.15
// Main.java
public class Main {
public native int intMethod(int i);
static {
// 启动时载入 libMain.dylib
System.loadLibrary("Main");
}
public static void main(String[] args) {
System.out.println(new Main().intMethod(2));
}
}
// Main.c:
// 将 Main.h 引入
#include "Main.h"
// 相当于继承 "Main.h" 的 Java_Main_intMethod
JNIEXPORT jint JNICALL Java_Main_intMethod(
JNIEnv *env, jobject obj, jint i)
{
return i * i;
}
Скомпилируйте и запустите:
// 同时生成 Main.class 和 Main.h
javac Main.java -h .
// 根据 Main.c 生成 libMain.dylib
gcc -dynamiclib -O3 \
-I/usr/include \
-I$JAVA_HOME/include \
-I$JAVA_HOME/include/darwin \
Main.c -o libMain.dylib
// 指定 library 的路径为当前路径
java -cp . -Djava.library.path=$(pwd) Main
вывод:
4
/* Main.h .h 作为头文件*/
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Main */
#ifndef _Included_Main
#define _Included_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Main
* Method: intMethod
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_Main_intMethod
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
javac Main.java -h .
// 可拆分为两个命令
javac Main.java
javah -jni Main
4.2 Принцип
При запуске Main.class загрузите libMain.dylib в виртуальную машину, JVM вызывает Java_Main_intMethod из libMain.dylib, передает параметры, libMain.dylib запускается непосредственно системой и возвращает результат.
- * env используется для преобразования между данными типа java и данными собственного типа (здесь язык C)
- Jint по-прежнему является типом данных Java.Основные типы данных Java могут быть отображены (использованы) без преобразования через *env
/*C code*/
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
/*Get the native string from javaString*/
const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
/*Do something with the nativeString*/
/*DON'T FORGET THIS LINE!!!*/
(*env)->ReleaseStringUTFChars(env, javaString, nativeString);
}
4.3 Ссылка
- A Simple Java Native Interface (JNI) example in Java and Scala
- Java native keyword example
- Java Native Interface: JNI Example
- Идеи программирования на Java — ПриложениеAиспользовать не-JAVAкод
- Собственный интерфейс Java — Вики
V. Резюме
Отражение Отражение, где отражено буквальное значение отражения?
Таким образом можно понять, что объект отражения получается нативным методом, а объектом отражения манипулируют, как зеркалом, оно будет отражаться на исходном объекте.
Мы обнаружили, что связь между отражением и собственными методами:
- Получить поле, метод, объект конструктора, реализацию метода native()
- Получить значение поля, установить и изменить значение поля, реализация метода native()
- Метод запуска, реализация метода native()
- Запускаем конструктор, реализован метод native()
Мы можем заключить, что,Отражение реализовано нативными методами.
Мы говорим реализовать функцию через отражение, мы также можем сказать
- Реализовано путем отражения
- Реализовано через API отражения
- Реализован нативным методом
Отражение — это нетрадиционный (реализация собственного метода) способ получения информации о файле класса, выполнения инструкций байт-кода файла класса и манипулирования объектными данными.
Резюме одним предложением: Reflection — это среда выполненияполучить и изменитьвозможности данных объекта.
О среде выполнения: Java — это статический язык, который сначала компилируется, а затем запускается. Код не выполняется во время компиляции, код выполняется во время выполнения.