Java提高班(六)反射和动态代理(JDK Proxy和Cglib)
反射和動(dòng)態(tài)代理放有一定的相關(guān)性,但單純的說動(dòng)態(tài)代理是由反射機(jī)制實(shí)現(xiàn)的,其實(shí)是不夠全面不準(zhǔn)確的,動(dòng)態(tài)代理是一種功能行為,而它的實(shí)現(xiàn)方法有很多。要怎么理解以上這句話,請看下文。
一、反射
反射機(jī)制是 Java 語言提供的一種基礎(chǔ)功能,賦予程序在運(yùn)行時(shí)自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者對象,比如獲取某個(gè)對象的類定義,獲取類聲明的屬性和方法,調(diào)用方法或者構(gòu)造對象,甚至可以運(yùn)行時(shí)修改類定義。
1、獲取類(Class)對象
獲取類對象有三種方法:
- 通過forName() -> 示例:Class.forName("PeopleImpl")
- 通過getClass() -> 示例:new PeopleImpl().getClass()
- 直接獲取.class -> 示例:PeopleImpl.class
2、類的常用方法
- getName():獲取類完整方法;
- getSuperclass():獲取類的父類;
- newInstance():創(chuàng)建實(shí)例對象;
- getFields():獲取當(dāng)前類和父類的public修飾的所有屬性;
- getDeclaredFields():獲取當(dāng)前類(不包含父類)的聲明的所有屬性;
- getMethod():獲取當(dāng)前類和父類的public修飾的所有方法;
- getDeclaredMethods():獲取當(dāng)前類(不包含父類)的聲明的所有方法;
更多方法:http://icdn.apigo.cn/blog/class-all-method.png
3、類方法調(diào)用
反射要調(diào)用類中的方法,需要通過關(guān)鍵方法“invoke()”實(shí)現(xiàn)的,方法調(diào)用也分為三種:
- 靜態(tài)(static)方法調(diào)用
- 普通方法調(diào)用
- 私有方法調(diào)用
以下會(huì)分別演示,各種調(diào)用的實(shí)現(xiàn)代碼,各種調(diào)用的公共代碼部分,如下:
// 此段代碼為公共代碼 interface People {int parentAge = 18;public void sayHi(String name); } class PeopleImpl implements People {private String privSex = "男";public String race = "漢族";@Overridepublic void sayHi(String name) {System.out.println("hello," + name);}private void prvSayHi() {System.out.println("prvSayHi~");}public static void getSex() {System.out.println("18歲");} }3.1 靜態(tài)方法調(diào)用
// 核心代碼(省略了拋出異常的聲明) public static void main(String[] args) {Class myClass = Class.forName("example.PeopleImpl");// 調(diào)用靜態(tài)(static)方法Method getSex = myClass.getMethod("getSex");getSex.invoke(myClass); }靜態(tài)方法的調(diào)用比較簡單,使用 getMethod(xx) 獲取到對應(yīng)的方法,直接使用 invoke(xx)就可以了。
3.2 普通方法調(diào)用
普通非靜態(tài)方法調(diào)用,需要先獲取類示例,通過“newInstance()”方法獲取,核心代碼如下:
Class myClass = Class.forName("example.PeopleImpl"); Object object = myClass.newInstance(); Method method = myClass.getMethod("sayHi",String.class); method.invoke(object,"老王");getMethod 獲取方法,可以聲明需要傳遞的參數(shù)的類型。
3.3 調(diào)用私有方法
調(diào)用私有方法,必須使用“getDeclaredMethod(xx)”獲取本類所有什么的方法,代碼如下:
Class myClass = Class.forName("example.PeopleImpl"); Object object = myClass.newInstance(); Method privSayHi = myClass.getDeclaredMethod("privSayHi"); privSayHi.setAccessible(true); // 修改訪問限制 privSayHi.invoke(object);除了“getDeclaredMethod(xx)”可以看出,調(diào)用私有方法的關(guān)鍵是設(shè)置 setAccessible(true) 屬性,修改訪問限制,這樣設(shè)置之后就可以進(jìn)行調(diào)用了。
4、總結(jié)
1.在反射中核心的方法是 newInstance() 獲取類實(shí)例,getMethod(..) 獲取方法,使用 invoke(..) 進(jìn)行方法調(diào)用,通過 setAccessible 修改私有變量/方法的訪問限制。
2.獲取屬性/方法的時(shí)候有無“Declared”的區(qū)別是,帶有 Declared 修飾的方法或?qū)傩?#xff0c;可以獲取本類的所有方法或?qū)傩?#xff08;private 到 public),但不能獲取到父類的任何信息;非 Declared 修飾的方法或?qū)傩?#xff0c;只能獲取 public 修飾的方法或?qū)傩?#xff0c;并可以獲取到父類的信息,比如 getMethod(..)和getDeclaredMethod(..)。
二、動(dòng)態(tài)代理
動(dòng)態(tài)代理是一種方便運(yùn)行時(shí)動(dòng)態(tài)構(gòu)建代理、動(dòng)態(tài)處理代理方法調(diào)用的機(jī)制,很多場景都是利用類似機(jī)制做到的,比如用來包裝 RPC 調(diào)用、面向切面的編程(AOP)。
實(shí)現(xiàn)動(dòng)態(tài)代理的方式很多,比如 JDK 自身提供的動(dòng)態(tài)代理,就是主要利用了上面提到的反射機(jī)制。還有其他的實(shí)現(xiàn)方式,比如利用傳說中更高性能的字節(jié)碼操作機(jī)制,類似 ASM、cglib(基于 ASM)等。
動(dòng)態(tài)代理解決的問題?
首先,它是一個(gè)代理機(jī)制。如果熟悉設(shè)計(jì)模式中的代理模式,我們會(huì)知道,代理可以看作是對調(diào)用目標(biāo)的一個(gè)包裝,這樣我們對目標(biāo)代碼的調(diào)用不是直接發(fā)生的,而是通過代理完成。通過代理可以讓調(diào)用者與實(shí)現(xiàn)者之間解耦。比如進(jìn)行 RPC 調(diào)用,通過代理,可以提供更加友善的界面。還可以通過代理,可以做一個(gè)全局的攔截器。
1、JDK Proxy 動(dòng)態(tài)代理
JDK Proxy 是通過實(shí)現(xiàn) InvocationHandler 接口來實(shí)現(xiàn)的,代碼如下:
interface Animal {void eat(); } class Dog implements Animal {@Overridepublic void eat() {System.out.println("The dog is eating");} } class Cat implements Animal {@Overridepublic void eat() {System.out.println("The cat is eating");} }// JDK 代理類 class AnimalProxy implements InvocationHandler {private Object target; // 代理對象public Object getInstance(Object target) {this.target = target;// 取得代理對象return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("調(diào)用前");Object result = method.invoke(target, args); // 方法調(diào)用System.out.println("調(diào)用后");return result;} }public static void main(String[] args) {// JDK 動(dòng)態(tài)代理調(diào)用AnimalProxy proxy = new AnimalProxy();Animal dogProxy = (Animal) proxy.getInstance(new Dog());dogProxy.eat(); }如上代碼,我們實(shí)現(xiàn)了通過動(dòng)態(tài)代理,在所有請求之前和之后打印了一個(gè)簡單的信息。
注意: JDK Proxy 只能代理實(shí)現(xiàn)接口的類(即使是extends繼承類也是不可以代理的)。
JDK Proxy 為什么只能代理實(shí)現(xiàn)接口的類?
這個(gè)問題要從動(dòng)態(tài)代理的實(shí)現(xiàn)方法 newProxyInstance 源碼說起:
@CallerSensitive public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException { // 省略其他代碼來看前兩個(gè)源碼參數(shù)說明:
* @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class to implement- loader:為類加載器,也就是 target.getClass().getClassLoader()
- interfaces:接口代理類的接口實(shí)現(xiàn)列表
所以這個(gè)問題的源頭,在于 JDK Proxy 的源碼設(shè)計(jì)。如果要執(zhí)意動(dòng)態(tài)代理,非接口實(shí)現(xiàn)類就會(huì)報(bào)錯(cuò):
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx
2、Cglib 動(dòng)態(tài)代理
JDK 動(dòng)態(tài)代理機(jī)制只能代理實(shí)現(xiàn)了接口的類,Cglib 是針對類來實(shí)現(xiàn)代理的,他的原理是對指定的目標(biāo)類生成一個(gè)子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但因?yàn)椴捎玫氖抢^承,所以不能對 final 修飾的類進(jìn)行代理。
Cglib 可以通過 Maven 直接進(jìn)行版本引用,Maven 版本地址:https://mvnrepository.com/artifact/cglib/cglib
本文使用的是最新版本 3.2.9 的 Cglib,在 pom.xml 添加如下引用:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.9</version> </dependency>Cglib 代碼實(shí)現(xiàn),如下:
class Panda {public void eat() {System.out.println("The panda is eating");} } class CglibProxy implements MethodInterceptor {private Object target; // 代理對象public Object getInstance(Object target) {this.target = target;Enhancer enhancer = new Enhancer();// 設(shè)置父類為實(shí)例類enhancer.setSuperclass(this.target.getClass());// 回調(diào)方法enhancer.setCallback(this);// 創(chuàng)建代理對象return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("調(diào)用前");Object result = methodProxy.invokeSuper(o, objects); // 執(zhí)行方法調(diào)用System.out.println("調(diào)用后");return result;} }public static void main(String[] args) {// CGLIB 動(dòng)態(tài)代理調(diào)用CglibProxy proxy = new CglibProxy();Panda panda = (Panda)proxy.getInstance(new Panda());panda.eat(); }cglib 的調(diào)用通過實(shí)現(xiàn) MethodInterceptor 接口的 intercept 方法,調(diào)用 invokeSuper 進(jìn)行動(dòng)態(tài)代理的,可以直接對普通類進(jìn)行動(dòng)態(tài)代理。
三、JDK Proxy VS Cglib
JDK Proxy 的優(yōu)勢:
- 最小化依賴關(guān)系,減少依賴意味著簡化開發(fā)和維護(hù),JDK 本身的支持,更加可靠;
- 平滑進(jìn)行 JDK 版本升級(jí),而字節(jié)碼類庫通常需要進(jìn)行更新以保證在新版上能夠使用;
Cglib 框架的優(yōu)勢:
- 可調(diào)用普通類,不需要實(shí)現(xiàn)接口;
- 高性能;
總結(jié): 需要注意的是,我們在選型中,性能未必是唯一考量,可靠性、可維護(hù)性、編程工作量等往往是更主要的考慮因素,畢竟標(biāo)準(zhǔn)類庫和反射編程的門檻要低得多,代碼量也是更加可控的,如果我們比較下不同開源項(xiàng)目在動(dòng)態(tài)代理開發(fā)上的投入,也能看到這一點(diǎn)。
本文所有示例代碼:https://github.com/vipstone/java-core-example.git
四、參考文檔
Java核心技術(shù)36講:http://t.cn/EwUJvWA
Java反射與動(dòng)態(tài)代理:https://www.cnblogs.com/hanganglin/p/4485999.html
轉(zhuǎn)載于:https://www.cnblogs.com/vipstone/p/10104020.html
總結(jié)
以上是生活随笔為你收集整理的Java提高班(六)反射和动态代理(JDK Proxy和Cglib)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分页工具类
- 下一篇: 人脸情绪识别系统第一次迭代总结