javascript
依赖注入底层反射原理_Spring中反射与反射的原理(转载)
Spring 中的反射與反射的原理 | Depp Wang's Blog
在造輪子:實(shí)現(xiàn)一個(gè)簡(jiǎn)易的 Spring IoC 容器一文中提到 Spring 在創(chuàng)建 Bean 實(shí)例和依賴(lài)注入時(shí)使用了反射,本文來(lái)具體分析一下 Spring 中的反射以及反射的原理。
一、Spring 中的反射
1.1、創(chuàng)建 Bean 實(shí)例時(shí)的反射// 通過(guò)類(lèi)加載器,根據(jù) class 路徑,得到其類(lèi)對(duì)象
Class> clz = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
// 根據(jù)類(lèi)對(duì)象生成 Bean 實(shí)例
return clz.newInstance();
反射體現(xiàn)在 clz.newInstance(); 中,核心代碼可分為兩部分:
1、利用反射獲取當(dāng)前類(lèi) PetStoreService 的所有構(gòu)造方法信息(Constructor 對(duì)象)
// java.lang.Class.java
// 調(diào)用 native 方法,此時(shí) publicOnly 為 false
res = getDeclaredConstructors0(publicOnly);
// native 方法,從 jvm 中的 class 文件中獲取構(gòu)造方法信息,再轉(zhuǎn)換為 Constructor 對(duì)象
private native Constructor[] getDeclaredConstructors0(boolean publicOnly);
2、利用反射通過(guò)默認(rèn)構(gòu)造方法生成實(shí)例
// sun.reflect.NativeConstructorAccessorImpl.java
// 調(diào)用 native 方法,var1 代表構(gòu)造方法的參數(shù),此時(shí)為 null
return newInstance0(this.c, var1);
// native 方法,真正生成實(shí)例的方法,執(zhí)行 class 文件的構(gòu)造方法
private static native Object newInstance0(Constructor> var0, Object[] var1);
1.2、構(gòu)造方法依賴(lài)注入時(shí)的反射// 通過(guò)反射獲取當(dāng)前類(lèi)所有的構(gòu)造方法信息(Constructor 對(duì)象)
Constructor>[] candidates = beanClass.getDeclaredConstructors();
// 設(shè)置構(gòu)造方法參數(shù)實(shí)例
Object[] argsToUse = new Object[parameterTypes.length];
argsToUse[i] = getBean(beanNames.get(i));
// 使用帶有參數(shù)的 Constructor 對(duì)象實(shí)現(xiàn)實(shí)例化 Bean。此時(shí)使用反射跟上面一樣(newInstance0),只是多了參數(shù)
return constructorToUse.newInstance(argsToUse);
1.3、setter() 方法依賴(lài)注入時(shí)的反射// 通過(guò)反射獲取當(dāng)前類(lèi)所有的方法信息(Method 對(duì)象)
Method[] methods = bean.getClass().getDeclaredMethods();
// 獲得方法參數(shù)實(shí)例
Object propertyBean = getBean(propertyName);
// 通過(guò)反射執(zhí)行調(diào)用 setter() 方法。invoke:調(diào)用方法,propertyBean 作為方法的參數(shù)
method.invoke(bean, propertyBean);
bean.getClass().getDeclaredMethods(); 中的核心代碼:
// java.lang.Class.java
// 調(diào)用 native 方法,publicOnly 為 false
getDeclaredMethods0(publicOnly);
// native 方法,從 jvm 中的 class 文件中獲取方法信息,再轉(zhuǎn)換為 Method
private native Method[] getDeclaredMethods0(boolean publicOnly);
method.invoke(bean, propertyBean); 中的核心代碼:
// sun.reflect.NativeMethodAccessorImpl.java
// 調(diào)用 native 方法,var1: bean、var2: propertyBean
return invoke0(this.method, var1, var2);
// native 方法,運(yùn)行 class 文件中的字節(jié)碼指令
private static native Object invoke0(Method var0, Object var1, Object[] var2);
1.4、@Autowired 依賴(lài)注入時(shí)的反射// 通過(guò)反射得到當(dāng)前類(lèi)所有的字段信息(Field 對(duì)象)
Field[] fields = bean.getClass().getDeclaredFields();
// 判斷字段是否有 @Autowired 注解
Annotation ann = field.getAnnotation(Autowired.class);
// 設(shè)置字段可連接,相當(dāng)于將非 public(private、default、protect)更改為 public
field.setAccessible(true);
// 通過(guò)反射設(shè)置字段的值
field.set(bean, getBean(field.getName()));
bean.getClass().getDeclaredFields(); 中的核心代碼:
// java.lang.Class.java
// 調(diào)用 native 方法,此時(shí) publicOnly 為 false
getDeclaredFields0(publicOnly);
// native 方法,從 jvm 中獲取 class 文件的字段信息,再轉(zhuǎn)換為 Field
private native Field[] getDeclaredFields0(boolean publicOnly);
field.set(bean, getBean(field.getName())); 中的核心代碼:
// sun.reflect.UnsafeObjectFieldAccessorImpl.java
// 調(diào)用 native 方法,將目標(biāo)對(duì)象 var1 指定偏移量 fieldOffset 處的字段值設(shè)置(修改)為 var2。var1 為 bean, var2 為參數(shù)實(shí)例
unsafe.putObject(var1, this.fieldOffset, var2);
// sun.misc.Unsafe.java
// native 方法,直接修改堆中對(duì)象字段的數(shù)據(jù)
public native void putObject(Object var1, long var2, Object var4);
二、class 文件與類(lèi)對(duì)象
class 文件由 java 文件編譯而來(lái),class 文件包含字段表、方法表、 方法(構(gòu)造方法)等。
當(dāng)類(lèi)加載器將 class 文件加載進(jìn)虛擬機(jī)元空間(Meta-space,jdk 1.8)時(shí),虛擬機(jī)在元空間中創(chuàng)建一個(gè)與之對(duì)應(yīng)的類(lèi)對(duì)象(Class 實(shí)例)。并將 class 文件由存放在磁盤(pán)的靜態(tài)結(jié)構(gòu)轉(zhuǎn)換為存放在內(nèi)存的運(yùn)行時(shí)結(jié)構(gòu)。
我們可以認(rèn)為一個(gè)類(lèi)(class 文件)對(duì)應(yīng)一個(gè)類(lèi)對(duì)象,當(dāng)前類(lèi)的所有對(duì)象共用一個(gè)類(lèi)對(duì)象。類(lèi)對(duì)象作為訪(fǎng)問(wèn)存放在 jvm 的 class 文件的入口。
package java.lang;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
public final class Class{
private native Field[] getDeclaredFields0(boolean publicOnly);
private native Method[] getDeclaredMethods0(boolean publicOnly);
private native Constructor[] getDeclaredConstructors0(boolean publicOnly);
// ReflectionData 緩存反射對(duì)象
private static class ReflectionData{
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor[] declaredConstructors;
volatile Constructor[] publicConstructors;
...
}
}
2.1、獲得類(lèi)對(duì)象的方式// 1、通過(guò)對(duì)象
Class cls = object.getClass();
// Object.java
public final native Class> getClass();
// 2、通過(guò)類(lèi)加載器
Class cls = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
// 3、通過(guò) Class 類(lèi),本質(zhì)上也是通過(guò)類(lèi)加載器
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 相關(guān)Field[] fields = cls.getFields(); // 獲取所有公共的 Field(包括父類(lèi))
Field[] fields = cls.getDeclaredFields(); // 獲取當(dāng)前類(lèi)的所有 Field(不包括父類(lèi)),包括公共和非公共
Field field = cls.getDeclaredField("fieldName"); // 指定獲取當(dāng)前類(lèi)某個(gè) Field
field.set(Object, Object); // 設(shè)置(修改)字段值
field.get(Object); // 獲取字段值
field.get(Object) 核心代碼:
// 調(diào)用 native 方法,獲取字段對(duì)應(yīng)的值
return unsafe.getObject(var1, this.fieldOffset);
// native 方法,從堆中獲取對(duì)象指定位置的對(duì)象
public native Object getObject(Object var1, long var2);
3.2、Method 相關(guān)Method[] methods = cls.getMethods(); // 獲取所有公共的 Method(包括父類(lèi))
Method[] methods = cls.getDeclaredMethods(); // 獲取當(dāng)前類(lèi)的所有 Method(不包括父類(lèi)),包括公共和非公共
method.invoke(Object instance, Object... parameters); // 運(yùn)行方法
運(yùn)行方法使用場(chǎng)景:要么是修改對(duì)象的數(shù)據(jù),如 void setter() 方法;要么是獲得執(zhí)行方法的返回結(jié)果。
String result = method.invoke().toString();
3.3、Constructor 相關(guān)Constructor>[] constructors = cls.getConstructors(); // 獲取所有公共的 Constructor(包括父類(lèi))
Constructor>[] constructors = cls.getDeclaredConstructors(); // 獲取當(dāng)前類(lèi)的所有Constructor(不包括父類(lèi)),包括公共和非公共
constructor.newInstance(Object... parameters); // 運(yùn)行構(gòu)造方法
當(dāng)沒(méi)有明確編寫(xiě)構(gòu)造方法,Java 編譯器將為該類(lèi)構(gòu)建一個(gè)默認(rèn)構(gòu)造函數(shù)
四、native 方法
Java 1.1 新增「Java 本地接口」(Java Native Interface,JNI),JNI 是一種包容極廣的編程接口,允許我們從 Java 應(yīng)用程序里調(diào)用 native 方法,native 方法由其它語(yǔ)言(C 、C++ 或匯編語(yǔ)言等)編寫(xiě)。native 方法用于實(shí)現(xiàn) Java 無(wú)法處理的功能。
4.1、簡(jiǎn)單示例
一個(gè)在 Java 中使用 Java 本地接口(JNI)的簡(jiǎn)單示例。
環(huán)境:jdk8、macOS 10.15
// Main.java
public class Main{
public native int intMethod(int i);
static {
// 啟動(dòng)時(shí)載入 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"
// 相當(dāng)于繼承 "Main.h" 的 Java_Main_intMethod
JNIEXPORT jint JNICALL Java_Main_intMethod(
JNIEnv *env, jobject obj, jint i)
{
return i * i;
}
編譯與運(yùn)行:
// 同時(shí)生成 Main.class 和 Main.h
javac Main.java -h .
// 根據(jù) 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 的路徑為當(dāng)前路徑
java -cp . -Djava.library.path=$(pwd) Main
輸出:
4
/* Main.h .h 作為頭文件*/
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* 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 .
// 可拆分為兩個(gè)命令
javac Main.java
javah -jni Main
4.2、原理
運(yùn)行 Main.class 時(shí),將 libMain.dylib 載入虛擬機(jī),JVM 調(diào)用 libMain.dylib 的 Java_Main_intMethod,傳入?yún)?shù),libMain.dylib 由系統(tǒng)直接運(yùn)行,返回結(jié)果。
*env 用于將 java 類(lèi)型數(shù)據(jù)與本地(此處為 C 語(yǔ)言)類(lèi)型數(shù)據(jù)之間的轉(zhuǎn)換
jint 還是 Java 數(shù)據(jù)類(lèi)型,Java 基本數(shù)據(jù)類(lèi)型可以映射(使用),不用通過(guò) *env 轉(zhuǎn)換
/*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、參考
五、總結(jié)
反射反射,哪里體現(xiàn)反射字面意思?
可以這么理解,通過(guò) native 方法得到反射對(duì)象,操作反射對(duì)象,像鏡子一樣,將反射到原對(duì)象上。
我們發(fā)現(xiàn),反射和 native 方法的關(guān)系:
獲取字段、方法、構(gòu)造方法對(duì)象,native() 方法實(shí)現(xiàn)
獲取字段值、設(shè)置修改字段值,native() 方法實(shí)現(xiàn)
運(yùn)行方法,native() 方法實(shí)現(xiàn)
運(yùn)行構(gòu)造方法,native() 方法實(shí)現(xiàn)
我們可以得出結(jié)論,反射由 native 方法實(shí)現(xiàn)。
我們說(shuō)通過(guò)反射實(shí)現(xiàn)一個(gè)功能,我們也可以說(shuō):
通過(guò)反射方法實(shí)現(xiàn)
通過(guò)反射 API 實(shí)現(xiàn)
通過(guò) native 方法實(shí)現(xiàn)
反射是一種非常規(guī)(native 方法實(shí)現(xiàn))方式獲取 class 文件信息、運(yùn)行 class 文件字節(jié)碼指令和操作對(duì)象數(shù)據(jù)的能力。
一句話(huà)總結(jié) :反射是一種運(yùn)行時(shí)獲取和修改對(duì)象數(shù)據(jù)的能力。
關(guān)于運(yùn)行時(shí):Java 是靜態(tài)語(yǔ)言,先編譯,后運(yùn)行。編譯時(shí)不執(zhí)行代碼,代碼都是運(yùn)行時(shí)執(zhí)行。
六、延伸閱讀
總結(jié)
以上是生活随笔為你收集整理的依赖注入底层反射原理_Spring中反射与反射的原理(转载)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python 列表拼接_【Python杂
- 下一篇: 北汽银翔m20解码器进什么车型?