Spring 中的反射与反射的原理 | Depp Wang's Blog
作者:DeppWang、原文地址
在造轮子:实现一个简易的 Spring IoC 容器一文中提到 Spring 在创建 Bean 实例和依赖注入时使用了反射,本文来具体分析一下 Spring 中的反射以及反射的原理。
一、Spring 中的反射
1.1、创建 Bean 实例时的反射
Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
return clz.newInstance();
|
反射体现在 clz.newInstance();
中,核心代码可分为两部分:
1、利用反射获取当前类 PetStoreService 的所有构造方法信息(Constructor 对象)
res = getDeclaredConstructors0(publicOnly);
private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
|
2、利用反射通过默认构造方法生成实例
return newInstance0(this.c, var1);
private static native Object newInstance0(Constructor<?> var0, Object[] var1);
|
1.2、构造方法依赖注入时的反射
Constructor<?>[] candidates = beanClass.getDeclaredConstructors();
Object[] argsToUse = new Object[parameterTypes.length]; argsToUse[i] = getBean(beanNames.get(i));
return constructorToUse.newInstance(argsToUse);
|
1.3、setter() 方法依赖注入时的反射
Method[] methods = bean.getClass().getDeclaredMethods();
Object propertyBean = getBean(propertyName);
method.invoke(bean, propertyBean);
|
bean.getClass().getDeclaredMethods(); 中的核心代码:
getDeclaredMethods0(publicOnly);
private native Method[] getDeclaredMethods0(boolean publicOnly);
|
method.invoke(bean, propertyBean); 中的核心代码:
return invoke0(this.method, var1, var2);
private static native Object invoke0(Method var0, Object var1, Object[] var2);
|
1.4、@Autowired 依赖注入时的反射
Field[] fields = bean.getClass().getDeclaredFields();
Annotation ann = field.getAnnotation(Autowired.class);
field.setAccessible(true);
field.set(bean, getBean(field.getName()));
|
bean.getClass().getDeclaredFields(); 中的核心代码:
getDeclaredFields0(publicOnly);
private native Field[] getDeclaredFields0(boolean publicOnly);
|
field.set(bean, getBean(field.getName())); 中的核心代码:
unsafe.putObject(var1, this.fieldOffset, var2);
public native void putObject(Object var1, long var2, Object var4);
|
二、class 文件与类对象
class 文件由 java 文件编译而来,class 文件包含字段表、方法表、<init>
方法(构造方法)等。
当类加载器将 class 文件加载进虚拟机元空间(Meta-space,jdk 1.8)时,虚拟机在元空间中创建一个与之对应的类对象(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);
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、获得类对象的方式
Class cls = object.getClass();
public final native Class<?> getClass();
Class cls = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
Class cls = Class.forName("org.deppwang.litespring.v1.service.PetStoreService");
private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller)
|
三、反射方法
以下是常用的反射方法。
3.1、Feild 相关
Field[] fields = cls.getFields(); Field[] fields = cls.getDeclaredFields(); Field field = cls.getDeclaredField("fieldName"); field.set(Object, Object); field.get(Object);
|
field.get(Object) 核心代码:
return unsafe.getObject(var1, this.fieldOffset);
public native Object getObject(Object var1, long var2);
|
3.2、Method 相关
Method[] methods = cls.getMethods(); Method[] methods = cls.getDeclaredMethods(); method.invoke(Object instance, Object... parameters);
|
运行方法使用场景:要么是修改对象的数据,如 void setter() 方法;要么是获得执行方法的返回结果。
String result = method.invoke().toString();
|
3.3、Constructor 相关
Constructor<?>[] constructors = cls.getConstructors(); Constructor<?>[] constructors = cls.getDeclaredConstructors(); 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)的简单示例。
public class Main { public native int intMethod(int i); static { System.loadLibrary("Main"); } public static void main(String[] args) { System.out.println(new Main().intMethod(2)); } }
|
#include "Main.h"
JNIEXPORT jint JNICALL Java_Main_intMethod( JNIEnv *env, jobject obj, jint i) { return i * i; }
|
编译与运行:
javac Main.java -h .
gcc -dynamiclib -O3 \ -I/usr/include \ -I$JAVA_HOME/include \ -I$JAVA_HOME/include/darwin \ Main.c -o libMain.dylib
java -cp . -Djava.library.path=$(pwd) Main
|
输出:
#include <jni.h>
#ifndef _Included_Main #define _Included_Main #ifdef __cplusplus extern "C" { #endif
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 转换
JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj, jstring javaString) { const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
(*env)->ReleaseStringUTFChars(env, javaString, nativeString); }
|
4.3、参考
五、总结
反射反射,哪里体现反射字面意思?
可以这么理解,通过 native 方法得到反射对象,操作反射对象,像镜子一样,将反射到原对象上。
我们发现,反射和 native 方法的关系:
- 获取字段、方法、构造方法对象,native() 方法实现
- 获取字段值、设置修改字段值,native() 方法实现
- 运行方法,native() 方法实现
- 运行构造方法,native() 方法实现
我们可以得出结论,反射由 native 方法实现。
我们说通过反射实现一个功能,我们也可以说:
- 通过反射方法实现
- 通过反射 API 实现
- 通过 native 方法实现
反射是一种非常规(native 方法实现)方式获取 class 文件信息、运行 class 文件字节码指令和操作对象数据的能力。
一句话总结 :反射是一种运行时获取和修改对象数据的能力。
关于运行时:Java 是静态语言,先编译,后运行。编译时不执行代码,代码都是运行时执行。
六、延伸阅读