天天看點

Java提高班(六)反射和動态代理(JDK Proxy和Cglib)

反射和動态代理放有一定的相關性,但單純的說動态代理是由反射機制實作的,其實是不夠全面不準确的,動态代理是一種功能行為,而它的實作方法有很多。要怎麼了解以上這句話,請看下文。

反射機制是 Java 語言提供的一種基礎功能,賦予程式在運作時自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者對象,比如擷取某個對象的類定義,擷取類聲明的屬性和方法,調用方法或者構造對象,甚至可以運作時修改類定義。

擷取類對象有三種方法:

通過forName() -> 示例:Class.forName("PeopleImpl")

通過getClass() -> 示例:new PeopleImpl().getClass()

直接擷取.class -> 示例:PeopleImpl.class

getName():擷取類完整方法;

getSuperclass():擷取類的父類;

newInstance():建立執行個體對象;

getFields():擷取目前類和父類的public修飾的所有屬性;

getDeclaredFields():擷取目前類(不包含父類)的聲明的所有屬性;

getMethod():擷取目前類和父類的public修飾的所有方法;

getDeclaredMethods():擷取目前類(不包含父類)的聲明的所有方法;

更多方法:http://icdn.apigo.cn/blog/class-all-method.png

反射要調用類中的方法,需要通過關鍵方法“invoke()”實作的,方法調用也分為三種:

靜态(static)方法調用

普通方法調用

私有方法調用

以下會分别示範,各種調用的實作代碼,各種調用的公共代碼部分,如下:

靜态方法的調用比較簡單,使用 getMethod(xx) 擷取到對應的方法,直接使用 invoke(xx)就可以了。

普通非靜态方法調用,需要先擷取類示例,通過“newInstance()”方法擷取,核心代碼如下:

getMethod 擷取方法,可以聲明需要傳遞的參數的類型。

調用私有方法,必須使用“getDeclaredMethod(xx)”擷取本類所有什麼的方法,代碼如下:

除了“getDeclaredMethod(xx)”可以看出,調用私有方法的關鍵是設定 setAccessible(true) 屬性,修改通路限制,這樣設定之後就可以進行調用了。

1.在反射中核心的方法是 newInstance() 擷取類執行個體,getMethod(..) 擷取方法,使用 invoke(..) 進行方法調用,通過 setAccessible 修改私有變量/方法的通路限制。

2.擷取屬性/方法的時候有無“Declared”的差別是,帶有 Declared 修飾的方法或屬性,可以擷取本類的所有方法或屬性(private 到 public),但不能擷取到父類的任何資訊;非 Declared 修飾的方法或屬性,隻能擷取 public 修飾的方法或屬性,并可以擷取到父類的資訊,比如 getMethod(..)和getDeclaredMethod(..)。

動态代理是一種友善運作時動态建構代理、動态處理代理方法調用的機制,很多場景都是利用類似機制做到的,比如用來包裝 RPC 調用、面向切面的程式設計(AOP)。

實作動态代理的方式很多,比如 JDK 自身提供的動态代理,就是主要利用了上面提到的反射機制。還有其他的實作方式,比如利用傳說中更高性能的位元組碼操作機制,類似 ASM、cglib(基于 ASM)等。

動态代了解決的問題?

首先,它是一個代理機制。如果熟悉設計模式中的代理模式,我們會知道,代理可以看作是對調用目标的一個包裝,這樣我們對目标代碼的調用不是直接發生的,而是通過代理完成。通過代理可以讓調用者與實作者之間解耦。比如進行 RPC 調用,通過代理,可以提供更加友善的界面。還可以通過代理,可以做一個全局的攔截器。

JDK Proxy 是通過實作 InvocationHandler 接口來實作的,代碼如下:

如上代碼,我們實作了通過動态代理,在所有請求之前和之後列印了一個簡單的資訊。

注意: JDK Proxy 隻能代理實作接口的類(即使是extends繼承類也是不可以代理的)。

JDK Proxy 為什麼隻能代理實作接口的類?

這個問題要從動态代理的實作方法 newProxyInstance 源碼說起:

來看前兩個源碼參數說明:

loader:為類加載器,也就是 target.getClass().getClassLoader()

interfaces:接口代理類的接口實作清單

是以這個問題的源頭,在于 JDK Proxy 的源碼設計。如果要執意動态代理,非接口實作類就會報錯:

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx

JDK 動态代理機制隻能代理實作了接口的類,Cglib 是針對類來實作代理的,他的原理是對指定的目标類生成一個子類,并覆寫其中方法實作增強,但因為采用的是繼承,是以不能對 final 修飾的類進行代理。

Cglib 可以通過 Maven 直接進行版本引用,Maven 版本位址:https://mvnrepository.com/artifact/cglib/cglib

本文使用的是最新版本 3.2.9 的 Cglib,在 pom.xml 添加如下引用:

Cglib 代碼實作,如下:

cglib 的調用通過實作 MethodInterceptor 接口的 intercept 方法,調用 invokeSuper 進行動态代理的,可以直接對普通類進行動态代理。

JDK Proxy 的優勢:

最小化依賴關系,減少依賴意味着簡化開發和維護,JDK 本身的支援,更加可靠;

平滑進行 JDK 版本更新,而位元組碼類庫通常需要進行更新以保證在新版上能夠使用;

Cglib 架構的優勢:

可調用普通類,不需要實作接口;

高性能;

總結: 需要注意的是,我們在選型中,性能未必是唯一考量,可靠性、可維護性、程式設計工作量等往往是更主要的考慮因素,畢竟标準類庫和反射程式設計的門檻要低得多,代碼量也是更加可控的,如果我們比較下不同開源項目在動态代理開發上的投入,也能看到這一點。

本文所有示例代碼:https://github.com/vipstone/java-core-example.git

Java核心技術36講:http://t.cn/EwUJvWA

Java反射與動态代理:https://www.cnblogs.com/hanganglin/p/4485999.html

關注下面二維碼,訂閱更多精彩内容。

Java提高班(六)反射和動态代理(JDK Proxy和Cglib)
Java提高班(六)反射和動态代理(JDK Proxy和Cglib)
Java提高班(六)反射和動态代理(JDK Proxy和Cglib)

關注公衆号(加好友):

Java提高班(六)反射和動态代理(JDK Proxy和Cglib)

作者:

王磊的部落格

出處:

http://vipstone.cnblogs.com/