萬字總結之反射(框架之魂)
前言
準備過年看下Spring原始碼,用來唬人,哈哈哈哈。正經點,是為了在遇到問題的時候,能知其然而知其所以然。但是在開始前,先惡補下基礎知識。今天看框架之魂——反射。
反射的概述(基礎部分開始)
反射是在編譯狀態,對某個類一無所知 ,但在執行狀態中,對於任意一個類,都能知道這個類的所有屬性和方法。
這個說太乾澀了,沒有靈魂,就像下面兩張圖。
所以咱來舉個例子,拒絕沒有靈魂。O(∩_∩)O哈哈~
為什麼要反射?
如果我們沒有Orange類,那該類在編譯的時候就會報錯找不到該類。這是我們平常使用的“正射”。這個名字是為了和反射相對應,不是官方的術語。
但是這存在著一個明顯的缺點,就是在main方法裡呼叫的是Apple類,並沒有呼叫Orange類,所以應該是可以正常呼叫的,當我想要呼叫Orange類的時候,再報錯即可。但是,事與願違,事情不是照著我們的想法而發展的。
我們需要一種在編譯時不檢查類的呼叫情況,只有在執行時,才根據相應的要求呼叫相應的類,這就是“反射”。
反射的用途
反射最重要的用途就是開發各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過 XML 檔案配置 Bean),為了保證框架的通用性,它們可能需要根據配置檔案載入不同的物件或類,呼叫不同的方法,這個時候就必須用到反射,執行時動態載入需要載入的物件。
舉一個例子,在運用 Struts 2 框架的開發中我們一般會在 struts.xml
裡去配置 Action
,比如:
<action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction" method="execute"> <result>/shop/shop-index.jsp</result> <result name="error">login.jsp</result> </action>
Action
建立了一種對映關係,當 View 層發出請求時,請求會被 StrutsPrepareAndExecuteFilter
攔截,然後 StrutsPrepareAndExecuteFilter
會去動態地建立 Action 例項。比如我們請求 login.action
,那麼 StrutsPrepareAndExecuteFilter
就會去解析struts.xml檔案,檢索action中name為login的Action,並根據class屬性建立SimpleLoginAction例項,並用invoke方法來呼叫execute方法,這個過程離不開反射。
獲取Class檔案物件的三種方式
萬事萬物都是物件。
Apple apple=new Apple();
中的apple為Apple的一個例項。那Apple物件是哪個的例項呢?
其實是Class類的例項。
我們可以看他的註釋,私有的構造方法,只有JVM才能建立物件。
如果我們能找到某個物件的Class類,即可以建立其例項。
- 靜態屬性class
- Object類的getClass方法,如果知道例項,直接呼叫其getClass方法。
- Class類的靜態方法forName(),引數為類的完整路徑
(推薦使用)
這裡需要注意,通過類的全路徑名獲取Class物件會丟擲一個異常,要用try....catch...捕獲異常。如果根據類路徑找不到這個類那麼就會丟擲這個異常,Class類中forName方法原始碼如下:
注:雖然寫了三種方式,但平常使用最多,最推薦的是第三種方式,因為第一種方式需要知道類,第二種方式需要知道例項,如果知道了這些,可以直接呼叫其方法和引數,沒必要再用Class來實現功能。舉個例子,你從北京去上海,第一種方式直達就行,第二種方式和第三種方式則是先從北京到雲南,再從雲南到上海,顯得太冗餘。
反射的使用
我們以Apple類為例,利用發射來獲取其引數和方法。其有三個引數,預設default引數color,公有public引數size,私有private引數price。三個構造方法,分別是預設default構造,公有public帶有三個引數的有參構造,私有帶有兩個引數的有參構造。六個setter/getter方法公有方法,分別是color的預設default隔離級別的setter/getter方法,size的public隔離級別的setter/getter方法,price的private隔離級別的setter/getter方法。toString和三個引數的setter/getter方法。最後還有一個public隔離級別的toString方法。這樣詳細展開的描述,看起來很複雜,其實很簡單的,具體程式碼如下:
package com.eastrobot.reflect; public class Apple extends Fruit{ String color;//預設default public int size; private int price; Apple() {//預設default System.out.println("Apple的無參構造"); } public Apple(String color, int size, int price) { this.color = color; this.size = size; this.price = price; System.out.println("Apple的有參構造——三個引數"); } private Apple(String color, int size) { this.color = color; this.size = size; this.price = 10; System.out.println("Apple的有參構造——兩個引數"); } @Override public String toString() { return "color:" + color + ",size:" + size + ",price:" + price; } //預設default String getColor() { return color; } public int getSize() { return size; } private int getPrice() { return price; } //預設default void setColor(String color) { this.color = color; } public void setSize(int size) { this.size = size; } private void setPrice(int price) { this.price = price; } }
繼承的父類Fruit,包括一個public型別的引數taste,和其public型別的setter/getter方法。
public class Fruit { public String taste; public String getTaste() { return taste; } public void setTaste(String taste) { this.taste = taste; } }
1.通過反射獲取所有引數 getDeclaredFields
System.out.println("getDeclaredFields**********"); Field[] fields=appleClass.getDeclaredFields(); for(Field field:fields){ System.out.println(field.toString()); }
執行結果如下:
注:不管何種隔離級別,getDeclaredFields都會獲取到所有引數。
2.通過反射獲取指定引數getDeclaredField
//指定引數 System.out.println("getDeclaredField**********"); Field colorField=appleClass.getDeclaredField("color"); System.out.println("color:"+colorField.toString()); Field sizeField=appleClass.getDeclaredField("size"); System.out.println("size:"+sizeField.toString()); Field priceField=appleClass.getDeclaredField("price"); System.out.println("price:"+priceField.toString());
執行結果如下:
注:不管何種隔離級別,getDeclaredField可以通過輸入值獲取指定引數。
3.通過反射獲取所有pubic型別的引數 getFields
System.out.println("getFields**********"); Field[] fields=appleClass.getFields(); for(Field field:fields){ System.out.println(field.toString()); }
執行結果如下:
注:只能通過反射獲取public型別的屬性,也包括繼承自父類的屬性。
4.通過反射獲取指定public型別的引數 getField
Field colorField=appleClass.getField("color"); System.out.println("color:"+colorField.toString());
執行結果如下:
-------------------手動分割線-------------------
Field sizeField=appleClass.getField("size"); System.out.println("size:"+sizeField.toString());
執行結果如下:
-------------------手動分割線-------------------
Field priceField=appleClass.getField("price"); System.out.println("price:"+priceField.toString());
執行結果如下:
注:只有public型別才能通過getField方法獲取到,其他型別均獲取不到。
看到這裡,有些小夥伴要問了,這是為啥,理由呢?咱不能死記硬背,這樣過兩天就忘了,記得不牢固,咱來瞅瞅底層幹了啥。
插曲:為什麼getFields和getField只能獲取public型別的欄位?
我們以getField為例,觀察getDeclaredField和getField的區別,可以看到兩者都呼叫了privateGetDeclaredFields方法,但是區別是getDeclaredField方法中的引數publicOnly是false,getField方法中的引數publicOnly為true。
getDeclaredField方法:
getField方法:
那privateGetDeclaredFields裡面幹了啥,我們看下。
我們可以看到如果為true,就取declaredPublicFields欄位,即public欄位,如果為false,就取DeclaredFields。
5.通過反射獲取所有方法 getDeclaredMethods
//所有方法 System.out.println("getDeclaredMethods**********"); Method[] methods=appleClass.getDeclaredMethods(); for(Method method:methods){ System.out.println(method.toString()); }
執行結果如下:
6.通過反射獲取指定方法 getDeclaredMethod
//指定方法 System.out.println("getDeclaredMethod**********"); //default Method getColorMethod=appleClass.getDeclaredMethod("getColor"); System.out.println("getColorMethod:"+getColorMethod.toString()); //public Method getSizeMethod=appleClass.getDeclaredMethod("getSize"); System.out.println("getSizeMethod:"+getSizeMethod.toString()); //private Method getPriceMethod=appleClass.getDeclaredMethod("getPrice"); System.out.println("getPriceMethod:"+getPriceMethod.toString()); //父類的public Method getTasteMethod=appleClass.getDeclaredMethod("getTaste"); System.out.println("getTasteMethod:"+getTasteMethod.toString());
執行結果如下:
注:getDeclaredMethod只能獲取自己定義的方法,不能獲取從父類的方法。
7.通過反射獲取所有public型別的方法 getMethods
//所有方法 System.out.println("getMethods**********"); Method[] methods=appleClass.getMethods(); for(Method method:methods){ System.out.println(method.toString()); }
執行結果如下:
注:getMethods可以通過反射獲取所有的public方法,包括父類的public方法。
8.通過反射獲取指定public型別的方法 getMethod
//指定方法 System.out.println("getMethod**********"); Method method=appleClass.getMethod("toString"); System.out.println(method.toString());
執行結果如下:
9.通過反射獲取所有構造方法 getDeclaredConstuctors
//構造方法 System.out.println("getDeclaredConstructors**********"); Constructor[] constructors=appleClass.getDeclaredConstructors(); for(Constructor constructor:constructors){ System.out.println(constructor.toString()); }
執行結果如下:
10.通過反射獲取某個帶引數的構造方法 getDeclaredConstructor
//指定構造方法 System.out.println("getDeclaredConstructor**********"); Class[] cArg = new Class[3]; cArg[0] = String.class; cArg[1] = int.class; cArg[2] = int.class; Constructor constructor=appleClass.getDeclaredConstructor(cArg); System.out.println(constructor.toString());
執行結果如下:
11.通過反射獲取所有public型別的構造方法getConstructors
System.out.println("getConstructors**********"); Constructor[] constructors=appleClass.getConstructors(); for(Constructor constructor:constructors){ System.out.println(constructor.toString()); }
執行結果:
12.通過反射獲取某個public型別的構造方法getConstructor
//構造方法 System.out.println("getConstructor**********"); Constructor constructor1 = appleClass.getConstructor(String.class,int.class,int.class); System.out.println("public型別的有參構造:" + constructor1.toString()); Constructor constructor2 = appleClass.getConstructor(String.class, int.class); System.out.println("private型別的有參構造:" + constructor2.toString());
執行結果:
13.通過無參構造來獲取該類物件 newInstance()
//呼叫無參構造建立物件 Class appleClass = Class.forName("com.eastrobot.reflect.Apple"); Apple apple=(Apple)appleClass.newInstance();
執行結果如下:
14.通過有參構造來獲取該類物件 newInstance(XXXX)
Constructor constructor=appleClass.getConstructor(String.class,int.class,int.class); Apple apple=(Apple)constructor.newInstance("紅色",10,5);
執行結果如下:
15.獲取類名包含包路徑 getName()
String name= appleClass.getName(); System.out.println("name:"+name);
執行結果如下:
16.獲取類名不包含包路徑 getSimpleName()
String simpleName =appleClass.getSimpleName(); System.out.println("simpleName:"+simpleName);
執行結果如下:
17.通過反射呼叫方法 invoke
//呼叫無參構造建立物件 Class appleClass = Class.forName("com.eastrobot.reflect.Apple"); //呼叫有參構造 Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class); Apple apple = (Apple) constructor.newInstance("紅色", 10, 20); //獲取toString方法並呼叫 Method method = appleClass.getDeclaredMethod("toString"); String str=(String)method.invoke(apple); System.out.println(str);
注:invoke+例項可以呼叫相關public方法。
18.判斷方法是否能呼叫isAccessible
//呼叫無參構造建立物件 Class appleClass = Class.forName("com.eastrobot.reflect.Apple"); //呼叫有參構造 Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class); Apple apple = (Apple) constructor.newInstance("紅色", 10, 20); //獲取public的getSize方法並呼叫 Method method = appleClass.getDeclaredMethod("getSize"); System.out.println("getSize方法的isAccessible:" + method.isAccessible()); int size = (Integer) method.invoke(apple); System.out.println("size:" + size); //獲取private的getPrice方法並呼叫 method = appleClass.getDeclaredMethod("getPrice"); System.out.println("getPrice的isAccessible:" + method.isAccessible()); int price = (Integer) method.invoke(apple); System.out.println("price:" + price);
執行結果:
注:這樣一看,public和private型別的isAccessible都為false,但是public型別的值可以獲取到,但是private型別的值並不能獲取到。其實isAccessible()值為 true或false,是指啟用和禁用訪問安全檢查的開關,如果為true,則取消安全檢查,為false,則執行安全檢查。如上,兩者都為false,說明兩者的進行了安全檢查,getSize為public型別,則可以獲取值,而getPrice為private,則不能獲取值。
19.設定安全檢查開關setAccessible
//呼叫無參構造建立物件 Class appleClass = Class.forName("com.eastrobot.reflect.Apple"); //呼叫有參構造 Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class); Apple apple = (Apple) constructor.newInstance("紅色", 10, 20); //獲取price Method otherMethod = appleClass.getDeclaredMethod("getPrice"); System.out.println("getPrice方法的isAccessible:" + otherMethod.isAccessible()); otherMethod.setAccessible(true); int price = (Integer) otherMethod.invoke(apple); System.out.println("之前的price:" + price); //重新設定price Method method = appleClass.getDeclaredMethod("setPrice", int.class); System.out.println("isAccessible:" + method.isAccessible()); method.setAccessible(true); method.invoke(apple, 100); //再次獲取price otherMethod = appleClass.getDeclaredMethod("getPrice"); otherMethod.setAccessible(true); price = (Integer) otherMethod.invoke(apple); System.out.println("之後的price:" + price);
執行結果:
注:setAccessible(true)表示取消安全檢查,setAccessible(false)表示啟用安全檢查。
常見面試題解答(進階部分開始)
被反射的類是否一定需要無參構造方法?
不一樣。因為有參構造方法也可以反射,具體程式碼如下:
Constructor constructor=appleClass.getConstructor(String.class,int.class,int.class); Apple apple=(Apple)constructor.newInstance("紅色",10,5);
反射的使用有什麼優勢和劣勢?
優勢:
在編譯時根本無法知道該物件或類可能屬於哪些類,程式只依靠執行時資訊來發現該物件和類的真實資訊。反射提高了Java程式的靈活性和擴充套件性,降低耦合性,提高自適應能力。它允許程式建立和控制任何類的物件,無需提前硬編碼目標類。
劣勢:
使用反射基本上是一種解釋操作,用於欄位和方法接入時要遠慢於直接程式碼
。
使用反射會模糊程式內部邏輯
,程式人員希望在原始碼中看到程式的邏輯,反射等繞過了原始碼的技術,因而會帶來維護問題。(這也就是看原始碼為什麼這麼難?哎。。。。)
為什麼說反射可以降低耦合?
因為反射不是硬編碼,在執行時可以靈活發現該類的詳細資訊,降低了程式碼之間的耦合性。
反射比較損耗效能,為什麼這樣說?(重點)
怎麼去判斷一個函式的效能?因為函式的執行太快太快了,你需要一個放慢鏡,這樣才能捕捉到他的速度。怎麼做?把一個函式執行一百萬遍或者一千萬遍,你才能真正瞭解一個函式的效能。也就是,你如果想判斷效能,你就不能還停留在秒級,毫秒級的概念。
如下是將直接獲取例項,直接獲取方法,反射獲取例項,反射獲取方法分別執行1百萬次所花費差。
try { //直接獲取例項 long startTime1 = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { new Apple(); } long endTime1 = System.currentTimeMillis(); System.out.println("直接獲取例項時間:" + (endTime1 - startTime1)); //直接獲取方法 long startTime2= System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { new Apple().toString(); } long endTime2 = System.currentTimeMillis(); System.out.println("直接獲取方法時間:" + (endTime2- startTime2)); //反射獲取例項 Class appleClass=Class.forName("com.eastrobot.reflect.Apple"); long startTime3 = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { appleClass.getDeclaredConstructor().newInstance(); } long endTime3 = System.currentTimeMillis(); System.out.println("反射獲取例項:" + (endTime3 - startTime3)); //反射獲取方法 Apple apple= (Apple)appleClass.getDeclaredConstructor().newInstance(); long startTime4 = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { Method method=appleClass.getMethod("toString"); method.invoke(apple); } long endTime4 = System.currentTimeMillis(); System.out.println("反射獲取方法:" + (endTime4 - startTime4));
執行結果截圖:
我們可以看到反射的確會導致效能問題,但反射導致的效能問題是否嚴重跟使用的次數有關係,如果控制在100次以內,基本上沒什麼差別,如果呼叫次數超過了100次,效能差異會很明顯。
打個比方,如果快遞員就在你住的小區,那麼你報一個地址:xx棟xx號,那麼快遞員就可以馬上知道你在哪裡,直接就去到你家門口;但是,如果快遞員是第一次來你們這裡,他是不是首先得查查百度地圖,看看怎麼開車過去,然後到了小區是不是得先問問物管xx棟怎麼找,然後,有可能轉在樓下轉了兩個圈才到了你的門前。
我們看上面這個場景,如果快遞員不熟悉你的小區,是不是會慢點,他的時間主要花費在了查詢百度地圖,詢問物業管理。OK,反射也是一樣,因為我事先什麼都不知道,所以我得花時間查詢一些其他資料,然後我才能找到你。
綜上,大部分我們使用反射是不考慮效能的,平常使用的次數較少,如果真的遇到效能問題,如反射的效率影響到程式邏輯,可以採用快取或Java位元組碼增強技術,參照庫有asm,也有第三方工具庫reflectAsm(https://github.com/EsotericSoftware/reflectasm)。
反射中的setAccessible()方法是否破壞了類的訪問規則
setAccessible(true)取消了Java的許可權控制檢查(注意不是改變方法或欄位的訪問許可權),對於setAccessible()方法是否會破壞類的訪問規則,產生安全隱患,見下:
反射原始碼解析
我們跟進Method的invoke方法,分為兩步,一是語言訪問級別是否為重寫,如果不是重寫則呼叫Reflection的quickCheckMemberAccess方法,即通過Modifiers 判斷是否具有訪問許可權,quickCheckMemberAccess方法主要是簡單地判斷 modifiers 是不是 public,如果不是的話就返回 false。所以 protected、private、default 修飾符都會返回 false,只有 public 都會返回 true。如果為false,則呼叫checkAccess方法。二是獲取MethodAccessor物件,並呼叫其invoke方法。
public final class Method extends AccessibleObject implements GenericDeclaration, Member { private volatile MethodAccessor methodAccessor; //每個Java方法只有一個對應Method物件作為root,這個root不會暴露給使用者, //而是每次通過反射獲取Method物件時新建立的Method物件將root包裝起來。 private Method root; @CallerSensitive public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; //在第一次呼叫一個實際Java方法應該的Method物件的invoke方法之前 //實現呼叫邏輯的MethodAccessor物件還沒有建立 //等到第一次呼叫時才建立MethodAccessor,並通過該MethodAccessor.invoke真正完成反射呼叫 if (ma == null) { ma = acquireMethodAccessor(); } //invoke並不是自己實現的反射呼叫邏輯,而是委託給sun.reflect.MethodAccessor來處理 return ma.invoke(obj, args); } ... private MethodAccessor acquireMethodAccessor() { MethodAccessor tmp = null; if (root != null) tmp = root.getMethodAccessor(); if (tmp != null) { methodAccessor = tmp; } else { //呼叫ReflectionFactory的newMethodAccessor方法,見下 tmp = reflectionFactory.newMethodAccessor(this); //更新root,以便下次直接使用 setMethodAccessor(tmp); } return tmp; } ... void setMethodAccessor(MethodAccessor accessor) { methodAccessor = accessor; // Propagate up if (root != null) { root.setMethodAccessor(accessor); } }
Reflection類:
public static boolean quickCheckMemberAccess(Class<?> memberClass, int modifiers) { return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers); }
ReflectionFactory類:
private static boolean noInflation = false; //選擇java版還是C語言版的閾值 private static int inflationThreshold = 15; public MethodAccessor newMethodAccessor(Method var1) { checkInitted(); if (noInflation) { //java版 return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers()); } else { //c語言版 NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1); DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2); var2.setParent(var3); return var3; } }
如上述程式碼所示,實際的MethodAccessor實現有兩個版本,一個是Java實現的,另一個是native code實現的。Java實現的版本在初始化時需要較多時間,但長久來說效能較好;native版本正好相反,啟動時相對較快,但執行時間長了之後速度就比不過Java版了。這是HotSpot的優化方式帶來的效能特性,同時也是許多虛擬機器的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機器難以分析也將其內聯,於是執行時間長了之後反而是託管版本的程式碼更快些。
為了權衡兩個版本的效能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射呼叫時,開頭若干次使用native版,等反射呼叫次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的位元組碼,以後對該Java方法的反射呼叫就會使用Java版。
Sun的JDK是從1.4系開始採用這種優化的,主要作者是Ken Russell
MethodAccessor的C語言實現(預設)
C語言版的MethodAccessor主要涉及這NativeMethodAccessorImpl和DelegatingMethodAccessorImpl兩個類,而DelegatingMethodAccessorImpl是間接層,不是太重要,就不貼程式碼啦。以下是NativeMethodAccessorImpl的程式碼,核心是invoke方法:
class NativeMethodAccessorImpl extends MethodAccessorImpl { private final Method method; private DelegatingMethodAccessorImpl parent; private int numInvocations; NativeMethodAccessorImpl(Method var1) { this.method = var1; } public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException { if (++this.numInvocations > ReflectionFactory.inflationThreshold()) { 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); } void setParent(DelegatingMethodAccessorImpl var1) { this.parent = var1; } private static native Object invoke0(Method var0, Object var1, Object[] var2); }
每次NativeMethodAccessorImpl.invoke()方法被呼叫時,都會增加一個呼叫次數計數器,看超過閾值沒有;一旦超過,則呼叫MethodAccessorGenerator.generateMethod()來生成Java版的MethodAccessor的實現類,並且改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版。後續經由DelegatingMethodAccessorImpl.invoke()呼叫到的就是Java版的實現了。
MethodAccessor的Java實現
return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
Java的MethodAccessor主要涉及的是MethodAccessorGenerator類,具體程式碼超長,只截取了部分程式碼,主要有三個方法,直接就是上述的generateMethod方法,程式碼如下:
public MethodAccessor generateMethod(Class var1, String var2, Class[] var3, Class var4, Class[] var5, int var6) { return (MethodAccessor)this.generate(var1, var2, var3, var4, var5, var6, false, false, (Class)null); } 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(); return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() { public MagicAccessorImpl run() { try { return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance(); } catch (InstantiationException var2) { throw (InternalError)(new InternalError()).initCause(var2); } catch (IllegalAccessException var3) { throw (InternalError)(new InternalError()).initCause(var3); } } }); } } private static synchronized String generateName(boolean var0, boolean var1) { int var2; if (var0) { if (var1) { var2 = ++serializationConstructorSymnum; return "sun/reflect/GeneratedSerializationConstructorAccessor" + var2; } else { var2 = ++constructorSymnum; return "sun/reflect/GeneratedConstructorAccessor" + var2; } } else { var2 = ++methodSymnum; return "sun/reflect/GeneratedMethodAccessor" + var2; } }
去閱讀原始碼的話,可以看到MethodAccessorGenerator是如何一點點把Java版的MethodAccessor實現類生產出來的,其實就是一個逐步解析的過程。
此時要注意的是最後的“sun/reflect/GeneratedMethodAccessor”+var2的程式碼。
例子
以上空說無用,太乾澀,咱來個例子。
public class Foo { public void foo(String name) { System.out.println("Hello, " + name); } }
public class test { public static void main(String[] args) { try { Class<?> clz = Class.forName("com.eastrobot.reflect.Foo"); Object o = clz.newInstance(); Method m = clz.getMethod("foo", String.class); for (int i = 0; i < 17; i++) { m.invoke(o, Integer.toString(i)); } } catch (Exception e) { } } }
除了上述程式碼,還需要在idea配置相關的執行引數,新增-XX:+TraceClassLoading引數,其為要求列印載入類的監控資訊。
我們先用上述的例子執行下,執行結果如下,前面十五次是正常的,到第16次的時候,出現了很多列印資訊,我已將一行標紅,“GeneratedMethodAccessor1”,這其實就是上面說的Java版獲取MethodAccessorGenerator的最後一行,1為自增引數。當第17次的時候,就不會用Java版的方式重新獲取,而是直接複用啦。
結語
終於結束了,邊玩邊寫,寫了五天,累死了,答應我,一定要好好看,好嗎?
如有說的不對地方,歡迎指正。
參考資料
Java反射詳細介紹
Java反射中getDeclaredField和getField的區別
java反射的使用場合和作用、及其優缺點
反射是否真的會讓你的程式效能降低?
深入解析Java反射(1) - 基礎
關於反射呼叫方法的一個log
反射進階,編寫反射程式碼值得注意的諸多細節