快速理解 Java 靜態(tài)代理 / 動態(tài)代理
理解Java動態(tài)代理需要對Java的反射機制有一定了解
什么是代理模式
在有些情況下,一個客戶不能或者不想直接訪問另一個對象,這時需要找一個中介幫忙完成某項任務,這個中介就是代理對象。
例如,購買火車票不一定要去火車站買,可以通過 12306 網(wǎng)站或者去火車票代售點買。又如找女朋友、找保姆、找工作等都可以通過找中介完成。
定義
由于某些原因需要給某對象提供一個代理以控制對該對象的訪問。
訪問對象不適合或者不能直接引用目標對象,代理對象作為訪問對象和目標對象之間的中介。
代理模式的主要角色
抽象角色(Subject):通過接口或抽象類聲明真實主題和代理對象實現(xiàn)的業(yè)務方法。
真實角色(Real Subject):實現(xiàn)了抽象主題中的具體業(yè)務,是代理對象所代表的真實對象,是最終要引用的對象。
代理(Proxy):提供了與真實主題相同的接口,其內(nèi)部含有對真實主題的引用,它可以訪問、控制或擴展真實主題的功能。
客戶 : 使用代理角色來進行一些操作 .
優(yōu)點
代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用
代理對象可以擴展目標對象的功能
代理模式能將客戶端與目標對象分離,在一定程度上降低了系統(tǒng)的耦合度,增加了程序的可擴展性
缺點
冗余,由于代理對象要實現(xiàn)與目標對象一致的接口,會產(chǎn)生過多的代理類。
系統(tǒng)設計中類的數(shù)量增加,變得難以維護。
使用動態(tài)代理方式,可以有效避免以上的缺點
靜態(tài)代理
靜態(tài)代理其實就是最基礎、最標準的代理模式實現(xiàn)方案。
舉例:
Rent . java 即抽象角色
//抽象角色:租房public interface Rent {public void rent();}
Landlord . java 即真實角色
//真實角色: 房東,房東要出租房子public class Landlord implements Rent{public void rent() {System.out.println("房屋出租");}}
Proxy . java 即代理
//代理角色:中介public class Proxy implements Rent {private Landlord landlord;public Proxy() { }public Proxy(Landlord landlord) {this.landlord = landlord;}//租房public void rent(){seeHouse();landlord.rent();fare();}//看房public void seeHouse(){System.out.println("帶房客看房");}//收中介費public void fare(){System.out.println("收中介費");}}Client . java 即客戶//客戶類,一般客戶都會去找代理!public class Client {public static void main(String[] args) {//房東要租房Landlord landlord = new Landlord();//中介幫助房東Proxy proxy = new Proxy(landlord);//客戶找中介proxy.rent();}}結(jié)果:帶房客看房房屋出租收中介費Process finished with exit code 0
在這個過程中,客戶接觸的是中介,看不到房東,但是依舊租到了房東的房子。同時房東省了心,客戶省了事。
靜態(tài)代理享受代理模式的優(yōu)點,同時也具有代理模式的缺點,那就是一旦實現(xiàn)的功能增加,將會變得異常冗余和復雜,秒變光頭。
為了保護頭發(fā),就出現(xiàn)了動態(tài)代理模式!
動態(tài)代理
動態(tài)代理的出現(xiàn)就是為了解決傳統(tǒng)靜態(tài)代理模式的中的缺點。
具備代理模式的優(yōu)點的同時,巧妙的解決了靜態(tài)代理代碼冗余,難以維護的缺點。
在Java中常用的有如下幾種方式:
JDK 原生動態(tài)代理
cglib 動態(tài)代理
javasist 動態(tài)代理
JDK原生動態(tài)代理
上例中靜態(tài)代理類中,中介作為房東的代理,實現(xiàn)了相同的租房接口。
例子
首先實現(xiàn)一個InvocationHandler,方法調(diào)用會被轉(zhuǎn)發(fā)到該類的invoke()方法。
然后在需要使用Rent的時候,通過JDK動態(tài)代理獲取Rent的代理對象。
class RentInvocationHandler implements InvocationHandler {private Rent rent;public RentInvocationHandler(Rent rent) {this.rent = rent;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {seeHouse();Object result = method.invoke(rent, args);fare();return result;}//看房public void seeHouse(){System.out.println("帶房客看房");}//收中介費public void fare(){System.out.println("收中介費");}//動態(tài)獲取代理public Object getProxy() {return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this); //核心關鍵}}客戶使用動態(tài)代理調(diào)用public class Client {public static void main(String[] args) {Landlord landlord = new Landlord();//代理實例的調(diào)用處理程序RentInvocationHandler pih = new RentInvocationHandler(landlord);Rent proxy = (Rent)pih.getProxy(); //動態(tài)生成對應的代理類!proxy.rent();}}
運行結(jié)果和前例相同
分析
上述代碼的核心關鍵是Proxy.newProxyInstance方法,該方法會根據(jù)指定的參數(shù)動態(tài)創(chuàng)建代理對象。
它三個參數(shù)的意義如下:
loader,指定代理對象的類加載器interfaces,代理對象需要實現(xiàn)的接口,可以同時指定多個接口handler,方法調(diào)用的實際處理者,代理對象的方法調(diào)用都會轉(zhuǎn)發(fā)到這里
Proxy.newProxyInstance會返回一個實現(xiàn)了指定接口的代理對象,對該對象的所有方法調(diào)用都會轉(zhuǎn)發(fā)給InvocationHandler.invoke()方法。
因此,在invoke()方法里我們可以加入任何邏輯,比如修改方法參數(shù),加入日志功能、安全檢查功能等等等等……
小結(jié)
顯而易見,對于靜態(tài)代理而言,我們需要手動編寫代碼讓代理實現(xiàn)抽象角色的接口。
而在動態(tài)代理中,我們可以讓程序在運行的時候自動在內(nèi)存中創(chuàng)建一個實現(xiàn)抽象角色接口的代理,而不需要去單獨定義這個類,代理對象是在程序運行時產(chǎn)生的,而不是編譯期。
對于從Object中繼承的方法,JDK Proxy會把
hashCode()、equals()、toString()這三個非接口方法轉(zhuǎn)發(fā)給InvocationHandler,其余的Object方法則不會轉(zhuǎn)發(fā)。
CGLIB動態(tài)代理
JDK動態(tài)代理是基于接口的,如果對象沒有實現(xiàn)接口該如何代理呢?CGLIB代理登場
CGLIB(Code Generation Library)是一個基于ASM的字節(jié)碼生成庫,它允許我們在運行時對字節(jié)碼進行修改和動態(tài)生成。CGLIB通過繼承方式實現(xiàn)代理。
使用cglib需要引入cglib的jar包,如果你已經(jīng)有spring-core的jar包,則無需引入,因為spring中包含了cglib。
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
例子
來看示例,假設我們有一個沒有實現(xiàn)任何接口的類Landlord:
public class Landlord{public void rent() {System.out.println("房屋出租");}}
因為沒有實現(xiàn)接口,所以使用通過CGLIB代理實現(xiàn)如下:
首先實現(xiàn)一個MethodInterceptor,方法調(diào)用會被轉(zhuǎn)發(fā)到該類的intercept()方法
public class RentMethodInterceptor implements MethodInterceptor {private Object target;//維護一個目標對象public RentMethodInterceptor(Object target) {this.target = target;}//為目標對象生成代理對象public Object getProxyInstance() {//工具類Enhancer en = new Enhancer();//設置父類en.setSuperclass(target.getClass());//設置回調(diào)函數(shù)en.setCallback(this);//創(chuàng)建子類對象代理return en.create();}public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("看房");// 執(zhí)行目標對象的方法Object returnValue = method.invoke(target, objects);System.out.println("中介費");return null;}}
客戶通過CGLIB動態(tài)代理獲取代理對象
public class Client {public static void main(String[] args) {Landlord target = new Landlord();System.out.println(target.getClass());//代理對象Landlord proxy = (Landlord) new RentMethodInterceptor(target).getProxyInstance();System.out.println(proxy.getClass());//執(zhí)行代理對象方法proxy.rent();}}
運行輸出結(jié)果和前例相同
分析
對于從Object中繼承的方法,CGLIB代理也會進行代理,如
hashCode()、equals()、toString()等,但是getClass()、wait()等方法不會,因為它是final方法,CGLIB無法代理。
其實CGLIB和JDK代理的思路大致相同
上述代碼中,通過CGLIB的Enhancer來指定要代理的目標對象、實際處理代理邏輯的對象。
最終通過調(diào)用create()方法得到代理對象,對這個對象所有非final方法的調(diào)用都會轉(zhuǎn)發(fā)給MethodInterceptor.intercept()方法。
在intercept()方法里我們可以加入任何邏輯,同JDK代理中的invoke()方法
通過調(diào)用MethodProxy.invokeSuper()方法,我們將調(diào)用轉(zhuǎn)發(fā)給原始對象,具體到本例,就是Landlord的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似,都是方法調(diào)用的中轉(zhuǎn)站。
final類型
CGLIB是通過繼承的方式來實現(xiàn)動態(tài)代理的,有繼承就不得不考慮final的問題。我們知道final類型不能有子類,所以CGLIB不能代理final類型,遇到這種情況會拋出類似如下異常:
java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete
同樣的,final方法是不能重載的,所以也不能通過CGLIB代理,遇到這種情況不會拋異常,而是會跳過final方法只代理其他方法。
其他方案
使用ASM在被代理類基礎上生成新的字節(jié)碼形成代理類
使用javassist在被代理類基礎上生成新的字節(jié)碼形成代理類
javassist也是常用的一種動態(tài)代理方案,ASM速度非常快,這里不在進行展開。
尾聲
動態(tài)代理是Spring AOP(Aspect Orient Programming, 面向切面編程)的實現(xiàn)方式,了解動態(tài)代理原理,對理解Spring AOP大有幫助。
如spring等這樣的框架,要增強具體業(yè)務的邏輯方法,不可能在框架里面去寫一個靜態(tài)代理類,太蠢了,只能按照用戶的注解或者xml配置來動態(tài)生成代理類。
業(yè)務代碼內(nèi),當需要增強的業(yè)務邏輯非常通用(如:添加log,重試,統(tǒng)一權限判斷等)時,使用動態(tài)代理將會非常簡單,如果每個方法增強邏輯不同,那么靜態(tài)代理更加適合。
使用靜態(tài)代理時,如果代理類和被代理類同時實現(xiàn)了一個接口,當接口方法有變動時,代理類也必須同時修改,代碼將變得臃腫且難以維護。
source: https://www.cnblogs.com/aduner/p/14646877.html