反射,它就像是一種魔法,引入運行時自省能力,賦予了 Java 語言使人意外的活力,經過運行時操做元數據或對象,Java 能夠靈活地操做運行時才能肯定的信息java
這裏筆者就深刻淺出總結下Java反射,如有不正確地方,感謝評論區指正交流~ 建議打開idea,寫一個Java反射的demo,跟着調試,效果會更好 :)設計模式
反射的概念是由Smith在1982年首次提出的,主要是指程序能夠訪問、檢測和修改它自己狀態或行爲的一種能力。有了反射,使Java相對於C、C++等語言就有了很強大的操做對象屬性及其方法的能力,注意,反射與直接調用對象方法和屬性相比,性能有必定的損耗,可是若是不是用在對性能有很強的場景下,反射都是一個很好且靈活的選擇。數組
說到反射,首先要了解什麼是Class。每一個類都會產生一個對應的Class對象,通常保存在.class文件中。全部類都是在對其第一次使用時,動態加載到JVM的,當程序建立一個對類的靜態成員的引用時,就會加載這個類。Class對象僅在須要的時候纔會加載,static初始化是在類加載時進行的。類加載時,類加載器首先會檢查這個類的Class對象是否已被加載過,若是還沒有加載,默認的類加載器就會根據類名查找對應的.class文件。網絡
任何一個Class文件都對應着惟一一個類或接口的信息(這裏的類包括抽象類哈),但反過來,類或接口信息並不必定都定義在文件裏(好比類或接口可能動態生成,Spring中AOP的實現中就有可能動態生成代理類)。Class文件是一組以8字節爲基礎單位的二進制文件,各個數據項嚴格按照順序緊湊着排列,中間幾乎沒有任何分隔符,也就是說整個Class文件存儲的幾乎都是程序運行所需的必要數據。框架
想在運行時使用類型信息,必須獲取對象(好比類Base對象)的Class對象的引用,使用Class.forName(「Base」)能夠實現該目的,或者使用Base.class。注意,有一點頗有趣,使用」.class」來建立Class對象的引用時,不會自動初始化該Class對應類,使用forName()會自動初始化該Class對應類。使用」.class」不會自動初始化是由於被延遲到了對靜態方法(構造器隱私地是靜態的)或者很是數靜態域進行首次引用時才進行。
爲了使用類而作的準備工做通常有如下3個步驟:jvm
若是不知道某個對象的確切類型,RTTI能夠告訴你,可是有一個前提:這個類型在編譯時必須已知,這樣才能使用RTTI來識別它。而Class類與java.lang.reflect類庫一塊兒對反射進行了支持,該類庫包含Field、Method和Constructor類,這些類的對象由JVM在啓動時建立,用以表示未知類裏對應的成員。ide
反射機制並無什麼神奇之處,當經過反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,看它屬於哪一個特定的類。所以,那個類的.class對於JVM來講必須是可獲取的,要麼在本地機器上,要麼從網絡獲取。因此對於RTTI和反射之間的真正區別只在於:模塊化
反射獲取對象中全部屬性值:函數
public class Person { private String name; private int age; // ... } Person person = new Person(); person.setName("luo"); person.setAge(25); try { Class clazz = person.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); System.out.println(field.getType() + " | " + field.getName() + " = " + field.get(person)); } // 經過反射獲取某一個方法 Method method = clazz.getMethod("setName", String.class); method.invoke(person, "bei"); } catch (Exception e) { e.printStackTrace(); }
日常的項目開發基本不多與反射打交道,由於框架已經幫咱們作了不少的事情了。但這不能說明反射機制沒有用,實際上有不少設計、開發都與反射機制有關,例如模塊化的開發,經過反射去調用對應的字節碼;動態代理設計模式也採用了反射機制,還有咱們平常使用的 Spring/Hibernate 等框架,也是利用CGLIB 反射機制才得以實現。工具
反射技術在在框架和中間件技術應用較多,有一句老話就是反射是Java框架的基石。典型的使用就是Spring的IoC實現,無論對象誰管理建立,只要我能用就行。再好比RPC技術能夠藉助於反射來實現,本地主機將要遠程調用的對象方法等信息發送給遠程主機,這些信息包括class名、方法名、方法參數類型、方法入參等,遠程主機接收到這些信息後就能夠藉助反射來獲取並執行對象方法,而後將結果返回便可。
說了那麼多,那麼Java反射是如何實現的呢?簡單來講Java反射就是靠JVM和Class相關類來實現的,Class相關類包括Field、Method和Constructor類等。類加載器加載完成一個類以後,會生成類對應的Class對象、Field對象、Method對象、Constructor對象,這些對象都保存在JVM(方法區)中,這也說明了反射必須在加載類以後進行的緣由。使用反射時,其實就是與上述所說的這幾個對象打交道呀(貌似Java反射也就這麼一回事哈)。
既然瞭解了Java反射原理,能夠試想一下C++爲何沒有反射呢,想讓C++擁有反射該如何作呢?Java相對於C++實現反射最重要的差異就是Java能夠依靠JVM這一悍將,能夠由JVM保存對象的相關信息,而後應用程序使用時直接從JVM中獲取使用。可是C++編譯後直接變成了機器碼了,貌似類或者對象的啥信息都沒了。。。 其實想讓C++有用反射能力,就須要保存可以操做類方法、類構造方法、類屬性的這些信息,這些信息要麼由應用程序本身來作,要麼由第三方工具來保存,而後應用程序使用從它那裏獲取,這些信息能夠經過(函數)指針來記錄,使用時經過指針來調用。
這裏咱們以Method.invoke流程來分析反射流程:
public class Person { private String name; private int age; public String getName() { return name; } // 其餘setter/getter方法 } public static void main(String[] args) throws Exception { Person person = new Person(); person.setName("luo"); person.setAge(26); for (int i = 0; i < 20; i++) { Method method = Person.class.getMethod("getName"); System.out.println(method.invoke(person)); } }
以上代碼經過反射調用person對象中的方法,下面跟着源碼看下Method.invoke的執行流程:
// Method public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { // 這裏會調用reflectionFactory.newMethodAccessor(this)建立一個新的MethodAccessor // 並賦值給methodAccessor,下次就不會進入到這裏了 // ma實際類型是DelegatingMethodAccessorImpl,代理對目標方法的調用 ma = acquireMethodAccessor(); } return ma.invoke(obj, args); } class DelegatingMethodAccessorImpl extends MethodAccessorImpl { private MethodAccessorImpl delegate; DelegatingMethodAccessorImpl(MethodAccessorImpl var1) { this.setDelegate(var1); } public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException { return this.delegate.invoke(var1, var2); } void setDelegate(MethodAccessorImpl var1) { this.delegate = var1; } } // NativeMethodAccessorImpl public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException { // ReflectionFactory.inflationThreshold()默認15,若是某一個Method反射調用超過15次, // 則自動生成GeneratedMethodAccessor賦值給DelegatingMethodAccessorImpl.delegate if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) { // 經過asm自動生成MethodAccessorImpl的實現類GeneratedMethodAccessor MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers()); this.parent.setDelegate(var3); } return invoke0(this.method, var1, var2); } // native方法,jni方式調用對應方法,調用的是對應的java方法 private static native Object invoke0(Method var0, Object var1, Object[] var2);
下面分別看下NativeMethodAccessorImpl和GeneratedMethodAccessor的調用棧信息:
Java默認在執行Method.invoke超過15次時(經過-Dsun.reflect.inflationThreshold可更改次數值,每一個方法對應一個Method對象),JVM會經過asm生成GeneratedMethodAccessor類,由該類調用對應的method方法。執行NativeMethodAccessorImpl.invoke是經過調用JNI方法,在JNI方法中再調用對應的java method方法,這種方式相對於使用GeneratedMethodAccessor.invoe方法來講,前者性能較弱,緣由有如下幾點:
在默認狀況下,方法的反射調用爲委派實現,委派給本地實現來進行方法調用。在調用超過 15 次以後,委派實現便會將委派對象切換至動態實現。這個動態的字節碼是在Java運行過程當中經過ASM自動生成的,它將直接使用 invoke 指令來調用目標方法。Java實現的版本在初始化時須要較多時間,但長久來講性能較好;native版本正好相反,啓動時相對較快,但運行時間長了以後速度就比不過Java版了。這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙做用,它就像個黑箱同樣讓虛擬機難以分析也將其內聯,因而運行時間長了以後反而是託管版本的代碼更快些。
默認method.invoke調用超過15次,會調用MethodAccessorGenerator.generate生成對應GeneratedMethodAccessorN類,代碼以下:
// MethodAccessorGenerator private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) { ByteVector var10 = ByteVectorFactory.create(); this.asm = new ClassFileAssembler(var10); this.declaringClass = var1; this.parameterTypes = var3; this.returnType = var4; this.modifiers = var6; this.isConstructor = var7; this.forSerialization = var8; this.asm.emitMagicAndVersion(); short var11 = 42; boolean var12 = this.usesPrimitiveTypes(); if (var12) { var11 = (short)(var11 + 72); } if (var8) { var11 = (short)(var11 + 2); } var11 += (short)(2 * this.numNonPrimitiveParameterTypes()); this.asm.emitShort(add(var11, (short)1)); final String var13 = generateName(var7, var8); this.asm.emitConstantPoolUTF8(var13); this.asm.emitConstantPoolClass(this.asm.cpi()); this.thisClass = this.asm.cpi(); if (var7) { if (var8) { this.asm.emitConstantPoolUTF8("sun/reflect/SerializationConstructorAccessorImpl"); } else { this.asm.emitConstantPoolUTF8("sun/reflect/ConstructorAccessorImpl"); } } else { this.asm.emitConstantPoolUTF8("sun/reflect/MethodAccessorImpl"); } this.asm.emitConstantPoolClass(this.asm.cpi()); this.superClass = this.asm.cpi(); this.asm.emitConstantPoolUTF8(getClassName(var1, false)); this.asm.emitConstantPoolClass(this.asm.cpi()); this.targetClass = this.asm.cpi(); short var14 = 0; if (var8) { this.asm.emitConstantPoolUTF8(getClassName(var9, false)); this.asm.emitConstantPoolClass(this.asm.cpi()); var14 = this.asm.cpi(); } this.asm.emitConstantPoolUTF8(var2); this.asm.emitConstantPoolUTF8(this.buildInternalSignature()); this.asm.emitConstantPoolNameAndType(sub(this.asm.cpi(), (short)1), this.asm.cpi()); if (this.isInterface()) { this.asm.emitConstantPoolInterfaceMethodref(this.targetClass, this.asm.cpi()); } else if (var8) { this.asm.emitConstantPoolMethodref(var14, this.asm.cpi()); } else { this.asm.emitConstantPoolMethodref(this.targetClass, this.asm.cpi()); } this.targetMethodRef = this.asm.cpi(); if (var7) { this.asm.emitConstantPoolUTF8("newInstance"); } else { this.asm.emitConstantPoolUTF8("invoke"); } this.invokeIdx = this.asm.cpi(); if (var7) { this.asm.emitConstantPoolUTF8("([Ljava/lang/Object;)Ljava/lang/Object;"); } else { this.asm.emitConstantPoolUTF8("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); } this.invokeDescriptorIdx = this.asm.cpi(); this.nonPrimitiveParametersBaseIdx = add(this.asm.cpi(), (short)2); for(int var15 = 0; var15 < var3.length; ++var15) { Class var16 = var3[var15]; if (!isPrimitive(var16)) { this.asm.emitConstantPoolUTF8(getClassName(var16, false)); this.asm.emitConstantPoolClass(this.asm.cpi()); } } this.emitCommonConstantPoolEntries(); if (var12) { this.emitBoxingContantPoolEntries(); } if (this.asm.cpi() != var11) { throw new InternalError("Adjust this code (cpi = " + this.asm.cpi() + ", numCPEntries = " + var11 + ")"); } else { this.asm.emitShort((short)1); this.asm.emitShort(this.thisClass); this.asm.emitShort(this.superClass); this.asm.emitShort((short)0); this.asm.emitShort((short)0); this.asm.emitShort((short)2); this.emitConstructor(); this.emitInvoke(); this.asm.emitShort((short)0); var10.trim(); final byte[] var17 = var10.getData(); // class數據 return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() { public MagicAccessorImpl run() { try { // 生成反射類GeneratedMethodAccessorN,對應的classLoader爲DelegatingClassLoader return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance(); } catch (IllegalAccessException | InstantiationException var2) { throw new InternalError(var2); } } }); } }
注意:生成對應GeneratedMethodAccessorN類,其對應的classLoader是DelegatingClassLoader,生成的GeneratedMethodAccessorN類是放在永久代的,那麼就會產生一個問題,若是數量過多,則會佔用永久代太多空間(java8中已沒有永久代空間,類數據放在直接內存中)。
生成的GeneratedMethodAccessorN類是什麼樣的呢?以下所示:
package sun.reflect; import com.luo.test.InvokeBean; import java.lang.reflect.InvocationTargetException; public class GeneratedMethodAccessor1 extends MethodAccessorImpl { public GeneratedMethodAccessor1() { } public Object invoke(Object var1, Object[] var2) throws InvocationTargetException { if (var1 == null) { throw new NullPointerException(); } else { InvokeBean var10000; Integer var10001; try { var10000 = (InvokeBean)var1; if (var2.length != 1) { throw new IllegalArgumentException(); } var10001 = (Integer)var2[0]; } catch (NullPointerException | ClassCastException var4) { throw new IllegalArgumentException(var4.toString()); } try { return var10000.test(var10001); } catch (Throwable var3) { throw new InvocationTargetException(var3); } } } }
其實就是直接調用目標對象的具體方法了,和正常的方法調用沒太大區別。關於GeneratedMethodAccessorN類加載器更多細節可點擊https://www.sohu.com/a/124124072_494943查看~
參考資料: