Java反射與動態代理的深入理解
一、什麼是反射機制?
反射的官方定義是這樣的:在運行狀態中,對於任意的一個類,都能夠知道這個類的所有屬性和方法,對任意一個對象都能夠通過反射機制調用一個類的任意方法,這種動態擷取類資訊及動態調用類對象方法的功能稱為Java的反射機制。
講的通俗一點的話就是,對於jvm來說,.java檔案必須要先編譯為.class檔案才能夠被jvm執行,所以在編譯為.class檔案的過程中,對象的類型都會被指定好,比如說 User user。那麼如果說我想在代碼啟動並執行過程中擷取到對象的類型呢?或者說程式在運行過程中如何載入一個特定的類呢?這就涉及到了java的反射機制了,反射提供了一套能夠讓我們在代碼運行時也能擷取到類型屬性的方法。
二、反射的使用
jdk提供了三種方式擷取一個對象的Class,就User user來說
1.user.getClass(),這個是Object類裡面的方法
2.User.Class屬性,任何的資料類型,基礎資料型別 (Elementary Data Type)或者抽象資料類型,都可以通過這種方式擷取類
3.Class.forName(""),Class類提供了這樣一個方法,讓我們通過類名來擷取到對象類
這三種方法用的最多的就是第三種,那麼擷取到類之後,Class類提供了很多擷取類屬性,方法,構造方法的api。
1.擷取所有的屬性
//擷取整個類
Class c = Class.forName("java.lang.Integer");
//擷取所有的屬性?
Field[] fs = c.getDeclaredFields();
//定義可變長的字串,用來儲存屬性
StringBuffer sb = new StringBuffer();
//通過追加的方法,將每個屬性拼接到此字串中
//最外邊的public定義
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");
//裡邊的每一個屬性
for(Field field:fs){
sb.append("\t");//空格
sb.append(Modifier.toString(field.getModifiers())+" ");//獲得屬性的修飾符,例如public,static等等
sb.append(field.getType().getSimpleName() + " ");//屬性的類型的名字
sb.append(field.getName()+";\n");//屬性的名字+斷行符號
}
sb.append("}");
System.out.println(sb);
2.擷取特定的屬性
//擷取類
Class c = Class.forName("User");
//擷取id屬性
Field idF = c.getDeclaredField("id");
//執行個體化這個類賦給o
Object o = c.newInstance();
//打破封裝
idF.setAccessible(true); //使用反射機制可以打破封裝性,導致了java對象的屬性不安全。
//給o對象的id屬性賦值"110"
idF.set(o, "110"); //set
//get
System.out.println(idF.get(o));
3.擷取方法
getDeclaredMethods() 擷取所有的方法
getReturnType() 獲得方法的放回類型
getParameterTypes() 獲得方法的傳入參數類型
getDeclaredMethod("方法名",參數類型.class,……) 獲得特定的方法
getDeclaredConstructors() 擷取所有的構造方法
getDeclaredConstructor(參數類型.class,……) 擷取特定的構造方法
getSuperclass() 擷取某類的父類
getInterfaces() 擷取某類實現的介面
三、反射的作用和動態代理
反射作用總結就是:1.動態地建立類的執行個體,將類綁定到現有的對象中,或從現有的對象中擷取類型。2.應用程式需要在運行時從某個特定的程式集中載入一個特定的類。
那麼什麼是動態代理呢?先給出百度的答案:動態代理,就是根據對象在記憶體中載入的Class類建立運行時類對象,從而調用代理類方法和屬性。
代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在用戶端和目標對象之間起到中介的作用。而代理模式又分為靜態代理和動態代理,先說靜態代理。
靜態代理通俗點將就是自己手寫一個代理類,而動態代理則不用我們手寫,而是依賴於java反射機制,下面以一個demo舉例。
demo結構
Subject是一個普通介面,裡面有個抽象的doSomething()的方法,而SubjectImpl.java是它的普通的實作類別,如下所示。
而HandProxy.java就是SubjectImpl的靜態代理類,代替了SubjectImpl完成doSomething的工作,如下所示
這樣做的好處是對於doSomething方法來說,我用靜態代理類來實現,可以任意的在其中插入我需要額外做的事情,比如說記錄日誌。
那麼AutoProxy.java就是動態代理類了,具體如下所示。
這裡面首先想要做到動態代理,必須先實現這個InvocationHandler介面,然後我們主要看bind方法,參數tar是一個Object類型的對象,也就是需要被代理的對象SubjectImpl,
方法裡面有一個Proxy類,這個Proxy類提供了很多方法,這裡我們用的是newProxyInstance方法,它有三個參數,第一個是被代理類的類構造器,第二個指的是被代理類的介面,也就是Subject介面,第三個是實現這個代理的類,這裡就是本類。具體的來說,這個方法執行了下面三步:
1.產生一個實現了參數interfaces裡所有介面且繼承了Proxy的代理類的位元組碼,然後用參數裡的classLoader載入這個代理類。
2.使用代理類父類的建構函式 Proxy(InvocationHandler h)來創造一個代理類的執行個體,將我們自訂的InvocationHandler的子類傳入。
3.返回這個代理類執行個體,因為我們構造的代理類實現了interfaces(也就是我們程式中傳入的subject.getClass().getInterfaces())裡的所有介面,因此返回的代理類可以強轉成Subject類型來調用介面中定義的方法。
而在調用每個代理類每個方法的時候,都用反射去調h的invoke方法(也就是我們自訂的InvocationHandler的子類中重寫的invoke方法),用參數傳遞了代理類執行個體、介面方法、調用參數列表,這樣我們在重寫的invoke方法中就可以實現對所有方法的統一封裝了。
總結一下,靜態代理的優點是清晰易懂,但是如果說業務代碼很多,那麼在代理類裡面必須全部重新調用一遍,很麻煩。而動態代理,利用java反射機制,動態產生了一個代理類,直接調用代理方法即可。
四、總結
反射這一塊是博主最近在看架構源碼的時候總是遇到這類問題所以刻意去整理了一下,裡面有些東西其實我也研究的不是很深刻,動態代理這邊的源碼還有待深入挖掘,若有大神發現寫的不對的地方,還請多多指教,謝謝。
本文永久更新連結地址:https://www.bkjia.com/Linux/2018-03/151419.htm