跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • Spring 中的反射与反射的原理
未分類
1 4 月 2020

Spring 中的反射与反射的原理

Spring 中的反射与反射的原理

資深大佬 : deppwxq 8

作者:DeppWang、原文地址

Spring 中的反射与反射的原理

在造轮子:实现一个简易的 Spring IoC 容器一文中提到 Spring 在创建 Bean 实例和依赖注入时使用了反射,本文来具体分析一下 Spring 中的反射以及反射的原理。

一、Spring 中的反射

1.1 、创建 Bean 实例时的反射

// 通过类加载器,根据 class 路径,得到其类对象 Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService"); // 根据类对象生成 Bean 实例 return clz.newInstance(); 

反射体现在 clz.newInstance(); 中,核心代码可分为两部分:

1 、利用反射获取当前类 PetStoreService 的所有构造方法信息( Constructor 对象)

// 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); 

二、class 文件与类对象

class 文件由 java 文件编译而来,class 文件包含字段表、方法表、<init> 方法(构造方法)等。

当类加载器将 class 文件加载进虚拟机元数据区(方法区,jdk1.7 )时,虚拟机创建一个与之对应的类对象( Class 实例)。并将 class 文件由存放在磁盘的静态结构转换为存放在内存的运行时结构。

我们可以认为一个类( class 文件)对应一个类对象,当前类的所有对象共用一个类对象。类对象作为访问存放在 jvm 的 class 文件的入口。

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.1 、Feild 相关

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 相关

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 相关

Constructor<?>[] constructors = cls.getConstructors(); // 获取所有公共的 Constructor (包括父类) Constructor<?>[] constructors = cls.getDeclaredConstructors(); // 获取当前类的所有 Constructor (不包括父类),包括公共和非公共 constructor.newInstance(Object... parameters); // 运行构造方法 

当没有明确编写构造方法,Java 编译器将为该类构建一个默认构造函数 <init>

四、native 方法

Java 1.1 新增「 Java 本地接口」(Java Native Interface,JNI),JNI 是一种包容极广的编程接口,允许我们从 Java 应用程序里调用 native 方法,native 方法由其它语言( C 、C++ 或汇编语言等)编写。native 方法用于实现 Java 无法处理的功能。

4.1 、简单示例

一个在 Java 中使用 Java 本地接口( JNI )的简单示例。

  • 环境:jdk8 、macOS 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 调用 libMain.dylib 的 Java_Main_intMethod,传入参数,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代码]( https://depp.wang/Java-Books/docs/Java 编程思想.pdf)
  • [Java 本地接口 – WiKi]( https://zh.wikipedia.org/wiki/Java 本地接口)

五、总结

反射反射,哪里体现反射字面意思?

可以这么理解,通过 native 方法得到反射对象,操作反射对象,像镜子一样,将反射到原对象上。

我们发现,反射和 native 方法的关系:

  • 获取字段、方法、构造方法对象,native() 方法实现
  • 获取字段值、设置修改字段值,native() 方法实现
  • 运行方法,native() 方法实现
  • 运行构造方法,native() 方法实现

我们可以得出结论,反射由 native 方法实现。

我们说通过反射实现一个功能,我们也可以说

  • 通过反射方法实现
  • 通过反射 API 实现
  • 通过 native 方法实现

Spring 中的反射与反射的原理

反射是一种非常规( native 方法实现)方式获取 class 文件信息、运行 class 文件字节码指令和操作对象数据的能力。

一句话总结 :反射是一种运行时获取和修改对象数据的能力。

关于运行时:Java 是静态语言,先编译,后运行。编译时不执行代码,代码都是运行时执行。

六、延伸阅读

  • JAVA 反射原理
  • 廖雪峰 Java 教程 – 反射
  • Reflection in Java
  • oracle reflect docs
  • What is reflection and why is it useful?
  • Understanding sun.misc.Unsafe
  • Guide to sun.misc.Unsafe
大佬有話說 (0)

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具