概述
由于某些原因需要給某對象提供一個代理以控制該對象的訪問。這時,訪問對象不適合或者不能直接引用目標對象,代理對象作為訪問對象與目標對象之間的中介。
Java中的代理按照代理類生成時機不同又分為靜態代理和動態代理。靜態代理代理類在編譯期就生成,而動態代理類則是在Java運行時動態生成。動態代理又有JDK代理和CGLib代理兩種。
結構
代理(Proxy)模式分為三種角色:
- 抽象主題(Subject)類:通過接口或抽象類聲明真實主題和代理對象實現的業務方法。
- 真實主題(Real Subject)類:實現了抽象主題中的具體業務,是代理對象所代表的真實對象,是最終要引用的對象。
- 代理(Proxy)類:提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制或拓展真實主題的功能。
靜態代理
package com.syn.proxy.static_proxy;/*賣票規范*/
public interface SellTickets {void sell();
}
package com.syn.proxy.static_proxy;/*火車站賣票*/
public class TrainStation implements SellTickets{@Overridepublic void sell() {System.out.println("火車站賣票.");}
}
package com.syn.proxy.static_proxy;/*代理點*/
public class Proxypoint implements SellTickets{private TrainStation trainStation = new TrainStation();@Overridepublic void sell() {System.out.println("代理收取費用.");trainStation.sell();}
}
package com.syn.proxy.static_proxy;/*客戶端買票*/
public class Client {public static void main(String[] args) {/*創建代理類*/Proxypoint proxypoint = new Proxypoint();/*買票*/proxypoint.sell();}
}
JDK動態代理
package com.syn.proxy.jdk_proxy;/*賣票規范*/
public interface SellTickets {void sell();
}
package com.syn.proxy.jdk_proxy;/*火車站賣票*/
public class TrainStation implements SellTickets {@Overridepublic void sell() {System.out.println("火車站賣票.");}
}
package com.syn.proxy.jdk_proxy;import com.syn.proxy.static_proxy.SellTickets;
import com.syn.proxy.static_proxy.TrainStation;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*獲取代理對象的工廠類*/
public class ProxyFactory {/*聲明目標對象*/private TrainStation trainStation = new TrainStation();public SellTickets getProxyObject(){/*返回代理對象*/SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(/*用于加載代理類,可以通過目標對象獲取類加載器*/trainStation.getClass().getClassLoader(),/*代理類實現的接口字節碼對象*/trainStation.getClass().getInterfaces(),/*代理對象的調用處理程序*/new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理收費(JDK動態版本).");/*執行目標對象方法*/Object object = method.invoke(trainStation,args);return object;}});return proxyObject;}
}
package com.syn.proxy.jdk_proxy;import com.syn.proxy.static_proxy.SellTickets;public class Client {public static void main(String[] args) {/*創建代理工廠*/ProxyFactory proxyFactory = new ProxyFactory();/*使用工廠獲取代理對象*/SellTickets proxyObject = proxyFactory.getProxyObject();/*調用目標方法*/proxyObject.sell();}
}
CDLIB動態代理
對于上面的案例,如果沒有定義SellTickets接口,只定義了TrainStation。很顯然JDK動態代理便無法使用了,因為JDK動態代理要求必須定義接口,對接口進行代理。
CGLIB是一個功能強大,高性能的代碼生成包,它為沒有實現接口的類提供代理,為JDK的動態代理提供了很好的補充。
需要引入maven包
<dependencies><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.1</version></dependency></dependencies>
package com.syn.proxy.cglib_proxy;/*火車站賣票*/
public class TrainStation{public void sell() {System.out.println("火車站賣票.");}
}
package com.syn.proxy.cglib_proxy;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*代理對象工廠*/
public class ProxyFactory implements MethodInterceptor {/*聲明火車站對象*/private TrainStation trainStation = new TrainStation();public TrainStation getProxyObject(){/*創建Enhancer對象,類似于JDK代理中的Proxy類*/Enhancer enhancer = new Enhancer();/*設置父類的字節碼對象*/enhancer.setSuperclass(TrainStation.class);/*設置回調函數*/enhancer.setCallback(this);/*創建代理對象*/return (TrainStation) enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("代理收費(CGLIB動態版本).");/*要調用目標對象的方法*/return method.invoke(trainStation,objects);}
}
package com.syn.proxy.cglib_proxy;public class Client {public static void main(String[] args) {/*創建代理工廠對象*/ProxyFactory proxyFactory = new ProxyFactory();/*獲取代理對象*/TrainStation proxyObject = proxyFactory.getProxyObject();/*調用代理對象的sell方法賣票*/proxyObject.sell();}
}
三種代理對比
- jdk代理和CGLIB代理
使用CGLib實現動態代理,CGLib底層采用ASM字節碼生成框架,使用字節碼技術生成代理類,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的類或者方法進行代理,因為CGLib原理是動態生成被代理類的子類。
在JDK1.6、JDK1.7、JDK1.8逐步對JDK動態代理優化之后,在調用次數較少的情況下,JDK代理效率高于CGLib代理效率,只有當進行大量調用的時候,JDK1.6和JDK1.7比CGLib代理效率低一點,但是到JDK1.8的時候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK動態代理,如果沒有接口使用CGLIB代理。
- 動態代理和靜態代理
動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。
如果接口增加一個方法,靜態代理模式除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。而動態代理不會出現該問題。
優缺點
優點:
- 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用;
- 代理對象可以擴展目標對象的功能;
- 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度。
缺點:
- 增加了系統的復雜度。