反射和動態(tài)代理放有一定的相關(guān)性,但單純的說動態(tài)代理是由反射機(jī)制實現(xiàn)的鬼吵,其實是不夠全面不準(zhǔn)確的,動態(tài)代理是一種功能行為篮赢,而它的實現(xiàn)方法有很多齿椅。要怎么理解以上這句話琉挖,請看下文。
一涣脚、反射
反射機(jī)制是 Java 語言提供的一種基礎(chǔ)功能示辈,賦予程序在運行時<strong>自省</strong>(introspect,官方用語)的能力遣蚀。通過反射我們可以直接操作類或者對象矾麻,比如獲取某個對象的類定義,獲取類聲明的屬性和方法芭梯,調(diào)用方法或者構(gòu)造對象险耀,甚至可以運行時修改類定義。
1玖喘、獲取類(Class)對象
獲取類對象有三種方法:
- 通過forName() -> 示例:Class.forName("PeopleImpl")
- 通過getClass() -> 示例:new PeopleImpl().getClass()
- 直接獲取.class -> 示例:PeopleImpl.class
2甩牺、類的常用方法
- getName():獲取類完整方法;
- getSuperclass():獲取類的父類累奈;
- newInstance():創(chuàng)建實例對象贬派;
- 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()”實現(xiàn)的突委,方法調(diào)用也分為三種:
- 靜態(tài)(static)方法調(diào)用
- 普通方法調(diào)用
- 私有方法調(diào)用
以下會分別演示柏卤,各種調(diào)用的實現(xiàn)代碼,各種調(diào)用的公共代碼部分匀油,如下:
// 此段代碼為公共代碼
interface People {
int parentAge = 18;
public void sayHi(String name);
}
class PeopleImpl implements People {
private String privSex = "男";
public String race = "漢族";
@Override
public 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() 獲取類實例拷恨,getMethod(..) 獲取方法脖律,使用 invoke(..) 進(jìn)行方法調(diào)用,通過 setAccessible 修改私有變量/方法的訪問限制腕侄。
2.獲取屬性/方法的時候有無“Declared”的區(qū)別是小泉,帶有 Declared 修飾的方法或?qū)傩裕梢垣@取本類的所有方法或?qū)傩裕╬rivate 到 public)兜挨,但不能獲取到父類的任何信息膏孟;非 Declared 修飾的方法或?qū)傩裕荒塬@取 public 修飾的方法或?qū)傩园杌悖⒖梢垣@取到父類的信息柒桑,比如 getMethod(..)和getDeclaredMethod(..)。
二噪舀、動態(tài)代理
動態(tài)代理是一種方便運行時動態(tài)構(gòu)建代理魁淳、動態(tài)處理代理方法調(diào)用的機(jī)制,很多場景都是利用類似機(jī)制做到的与倡,比如用來包裝 RPC 調(diào)用界逛、面向切面的編程(AOP)。
實現(xiàn)動態(tài)代理的方式很多纺座,比如 JDK 自身提供的動態(tài)代理息拜,就是主要利用了上面提到的反射機(jī)制。還有其他的實現(xiàn)方式净响,比如利用傳說中更高性能的字節(jié)碼操作機(jī)制少欺,類似 ASM、cglib(基于 ASM)等馋贤。
動態(tài)代理解決的問題赞别?
首先,它是一個<strong>代理機(jī)制</strong>配乓。如果熟悉設(shè)計模式中的代理模式仿滔,我們會知道,代理可以看作是對調(diào)用目標(biāo)的一個包裝犹芹,這樣我們對目標(biāo)代碼的調(diào)用不是直接發(fā)生的崎页,而是通過代理完成。通過代理可以讓調(diào)用者與實現(xiàn)者之間<strong>解耦</strong>腰埂。比如進(jìn)行 RPC 調(diào)用实昨,通過代理,可以提供更加友善的界面盐固。還可以通過代理荒给,可以做一個全局的攔截器丈挟。
1、JDK Proxy 動態(tài)代理
JDK Proxy 是通過實現(xiàn) InvocationHandler 接口來實現(xiàn)的志电,代碼如下:
interface Animal {
void eat();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("The dog is eating");
}
}
class Cat implements Animal {
@Override
public 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);
}
@Override
public 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 動態(tài)代理調(diào)用
AnimalProxy proxy = new AnimalProxy();
Animal dogProxy = (Animal) proxy.getInstance(new Dog());
dogProxy.eat();
}
如上代碼曙咽,我們實現(xiàn)了通過動態(tài)代理,在所有請求之前和之后打印了一個簡單的信息挑辆。
注意: JDK Proxy 只能代理實現(xiàn)接口的類(即使是extends繼承類也是不可以代理的)例朱。
JDK Proxy 為什么只能代理實現(xiàn)接口的類?
這個問題要從動態(tài)代理的實現(xiàn)方法 newProxyInstance 源碼說起:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 省略其他代碼
來看前兩個源碼參數(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:接口代理類的接口實現(xiàn)列表
所以這個問題的源頭洒嗤,在于 JDK Proxy 的源碼設(shè)計。如果要執(zhí)意動態(tài)代理魁亦,非接口實現(xiàn)類就會報錯:
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx
2渔隶、Cglib 動態(tài)代理
JDK 動態(tài)代理機(jī)制只能代理實現(xiàn)了接口的類,Cglib 是針對類來實現(xiàn)代理的洁奈,他的原理是對指定的目標(biāo)類生成一個子類间唉,并覆蓋其中方法實現(xiàn)增強(qiáng),但因為采用的是繼承利术,所以不能對 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 代碼實現(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è)置父類為實例類
enhancer.setSuperclass(this.target.getClass());
// 回調(diào)方法
enhancer.setCallback(this);
// 創(chuàng)建代理對象
return enhancer.create();
}
@Override
public 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 動態(tài)代理調(diào)用
CglibProxy proxy = new CglibProxy();
Panda panda = (Panda)proxy.getInstance(new Panda());
panda.eat();
}
cglib 的調(diào)用通過實現(xiàn) MethodInterceptor 接口的 intercept 方法,調(diào)用 invokeSuper 進(jìn)行動態(tài)代理的轮蜕,可以直接對普通類進(jìn)行動態(tài)代理昨悼。
三、JDK Proxy VS Cglib
JDK Proxy 的優(yōu)勢:
- 最小化依賴關(guān)系肠虽,減少依賴意味著簡化開發(fā)和維護(hù),JDK 本身的支持玛追,更加可靠税课;
- 平滑進(jìn)行 JDK 版本升級,而字節(jié)碼類庫通常需要進(jìn)行更新以保證在新版上能夠使用痊剖;
Cglib 框架的優(yōu)勢:
- 可調(diào)用普通類韩玩,不需要實現(xiàn)接口;
- 高性能陆馁;
總結(jié): 需要注意的是找颓,我們在選型中,性能未必是唯一考量叮贩,可靠性击狮、可維護(hù)性佛析、編程工作量等往往是更主要的考慮因素,畢竟標(biāo)準(zhǔn)類庫和反射編程的門檻要低得多彪蓬,代碼量也是更加可控的寸莫,如果我們比較下不同開源項目在動態(tài)代理開發(fā)上的投入,也能看到這一點档冬。
本文所有示例代碼:https://github.com/vipstone/java-core-example.git
四膘茎、參考文檔
Java核心技術(shù)36講:http://t.cn/EwUJvWA
Java反射與動態(tài)代理:https://www.cnblogs.com/hanganglin/p/4485999.html
關(guān)注作者公眾號:
如果覺得本文對您有幫助,請我喝杯咖啡吧酷誓。