代理模式——静态代理,动态代理(JDK代理和CGLib代理)
概述
由于某些原因需要給某對象提供一個代理以控制對該對象的訪問。
這時,訪問對象不適合或者不能直接引用目標對象,代理對象作為訪問對象和目標對象之間的中介。
Java中的代理按照代理類生成時機不同又分為靜態代理和動態代理。
靜態代理代理類在編譯期就生成,而動態代理代理類則是在Java運行時動態生成。
動態代理又有JDK代理和CGLib代理兩種。
結構
代理(Proxy)模式分為三種角色:
- 抽象主題(Subject)類: 通過接口或抽象類聲明真實主題和代理對象實現的業務方法。
- 真實主題(Real Subject)類: 實現了抽象主題中的具體業務,是代理對象所代表的真實對象,是最終要引用的對象。
- 代理(Proxy)類 : 提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制或擴展真實主題的功能。
靜態代理
我們通過案例來感受一下靜態代理。
【例】火車站賣票
如果要買火車票的話,需要去火車站買票,坐車到火車站,排隊等一系列的操作,顯然比較麻煩。而火車站在多個地方都有代售點,我們去代售點買票就方便很多了。
這個例子其實就是典型的代理模式,火車站是目標對象,代售點是代理對象。
類圖如下:
SellTickets.java
package com.itheima.pattern.proxy.static_proxy;/*** @version v1.0* @ClassName: SellTickets* @Description: 賣火車票的接口* @Author: dym*/ public interface SellTickets {void sell(); }TrainStation.java
package com.itheima.pattern.proxy.static_proxy;/*** @version v1.0* @ClassName: TrainStation* @Description: 火車站類* @Author: dym*/ public class TrainStation implements SellTickets {public void sell() {System.out.println("火車站賣票");} }ProxyPoint.java
package com.itheima.pattern.proxy.static_proxy;/*** @version v1.0* @ClassName: ProxyPoint* @Description: 代售點類* @Author: dym*/ public class ProxyPoint implements SellTickets {//聲明火車站類對象private TrainStation trainStation = new TrainStation();public void sell() {System.out.println("代售點收取一些服務費用");trainStation.sell();}}Client.java
package com.itheima.pattern.proxy.static_proxy;/*** @version v1.0* @ClassName: Client* @Description: TODO(一句話描述該類的功能)* @Author: dym*/ public class Client {public static void main(String[] args) {//創建代售點類對象ProxyPoint proxyPoint = new ProxyPoint();//調用方法進行買票proxyPoint.sell();} }從上面代碼中可以看出測試類直接訪問的是ProxyPoint類對象,
也就是說ProxyPoint作為訪問對象和目標對象的中介。
同時也對sell方法進行了增強(代理點收取一些服務費用)。
JDK動態代理
接下來我們使用動態代理實現上面案例,先說說JDK提供的動態代理。
Java中提供了一個動態代理類Proxy,Proxy并不是我們上述所說的代理對象的類,
而是提供了一個創建代理對象的靜態方法(newProxyInstance方法)來獲取代理對象。
代碼如下:
SellTickets.java
package com.itheima.pattern.proxy.jdk_proxy;/*** @version v1.0* @ClassName: SellTickets* @Description: 賣火車票的接口* @Author: dym*/ public interface SellTickets {void sell(); }TrainStation.java
package com.itheima.pattern.proxy.jdk_proxy;/*** @version v1.0* @ClassName: TrainStation* @Description: 火車站類* @Author: dym*/ public class TrainStation implements SellTickets {public void sell() {System.out.println("火車站賣票");} }ProxyFactory.java
package com.itheima.pattern.proxy.jdk_proxy;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;/*** @version v1.0* @ClassName: ProxyFactory* @Description: 獲取代理對象的工廠類* 代理類也實現了對應的接口* @Author: dym*/ public class ProxyFactory {//聲明目標對象private TrainStation station = new TrainStation();//獲取代理對象的方法public SellTickets getProxyObject() {//返回代理對象/*ClassLoader loader : 類加載器,用于加載代理類。可以通過目標對象獲取類加載器Class<?>[] interfaces : 代理類實現的接口的字節碼對象InvocationHandler h : 代理對象的調用處理程序*/SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {/*Object proxy : 代理對象。和proxyObject對象是同一個對象,在invoke方法中基本不用Method method : 對接口中的方法進行封裝的method對象Object[] args : 調用方法的實際參數返回值: 方法的返回值。*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//System.out.println("invoke方法執行了");System.out.println("代售點收取一定的服務費用(jdk動態代理)");//執行目標對象的方法Object obj = method.invoke(station, args);return obj;}});return proxyObject;} }Client.java
package com.itheima.pattern.proxy.jdk_proxy;/*** @version v1.0* @ClassName: Client* @Description: TODO(一句話描述該類的功能)* @Author: dym*/ public class Client {public static void main(String[] args) {//獲取代理對象//1,創建代理工廠對象ProxyFactory factory = new ProxyFactory();//2,使用factory對象的方法獲取代理對象SellTickets proxyObject = factory.getProxyObject();//3,調用賣調用的方法proxyObject.sell();System.out.println(proxyObject.getClass());} }使用了動態代理,我們思考下面問題:
-
ProxyFactory是代理類嗎?
ProxyFactory不是代理模式中所說的代理類,
-
而代理類是程序在運行過程中動態的在內存中生成的類。
-
通過阿里巴巴開源的 Java 診斷工具(Arthas【阿爾薩斯】)查看代理類的結構:
-
從上面的類中,我們可以看到以下幾個信息:
- 代理類($Proxy0)實現了SellTickets。這也就印證了我們之前說的真實類和代理類實現同樣的接口。
- 代理類($Proxy0)將我們提供了的匿名內部類對象傳遞給了父類。
-
動態代理的執行流程是什么樣?
下面是摘取的重點代碼:
執行流程如下:
CGLIB動態代理
同樣是上面的案例,我們再次使用CGLIB代理實現。
如果沒有定義SellTickets接口,只定義了TrainStation(火車站類)。
很顯然JDK代理是無法使用了,因為JDK動態代理要求必須定義接口,對接口進行代理。
CGLIB是一個功能強大,高性能的代碼生成包。
它為沒有實現接口的類提供代理,為JDK的動態代理提供了很好的補充。
CGLIB是第三方提供的包,所以需要引入jar包的坐標:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version> </dependency>TrainStation.java
package com.itheima.pattern.proxy.cglib_proxy;/*** @version v1.0* @ClassName: TrainStation* @Description: 火車站類* @Author: dym*/ public class TrainStation {public void sell() {System.out.println("火車站賣票");} }ProxyFactory.java
package com.itheima.pattern.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;/*** @version v1.0* @ClassName: ProxyFactory* @Description: 代理對象工廠,用來獲取代理對象* @Author: dym*/ public class ProxyFactory implements MethodInterceptor {//聲明火車站對象private TrainStation station = new TrainStation();public TrainStation getProxyObject() {//創建Enhancer對象,類似于JDK代理中的Proxy類Enhancer enhancer = new Enhancer();//設置父類的字節碼對象。指定父類enhancer.setSuperclass(TrainStation.class);//設置回調函數enhancer.setCallback(this);//創建代理對象TrainStation proxyObject = (TrainStation) enhancer.create();return proxyObject;}public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {//System.out.println("方法執行了");System.out.println("代售點收取一定的服務費用(CGLib代理)");//要調用目標對象的方法Object obj = method.invoke(station, objects);return obj;} }Client.java
package com.itheima.pattern.proxy.cglib_proxy;/*** @version v1.0* @ClassName: Client* @Description: TODO(一句話描述該類的功能)* @Author: dym*/ public class Client {public static void main(String[] args) {//創建代理工廠對象ProxyFactory factory = new ProxyFactory();//獲取代理對象TrainStation proxyObject = factory.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)。
這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,
而不需要像靜態代理那樣每一個方法進行中轉,如果接口增加一個方法,靜態代理模式除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。
而動態代理不會出現該問題
?
優缺點
優點:
- 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用;
- 代理對象可以擴展目標對象的功能;
- 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度;
缺點:
- 增加了系統的復雜度;
使用場景
-
遠程(Remote)代理
本地服務通過網絡請求遠程服務。為了實現本地到遠程的通信,我們需要實現網絡通信,處理其中可能的異常。為良好的代碼設計和可維護性,我們將網絡通信部分隱藏起來,只暴露給本地服務一個接口,通過該接口即可訪問遠程服務提供的功能,而不必過多關心通信部分的細節。
-
防火墻(Firewall)代理
當你將瀏覽器配置成使用代理功能時,防火墻就將你的瀏覽器的請求轉給互聯網;當互聯網返回響應時,代理服務器再把它轉給你的瀏覽器。
-
保護(Protect or Access)代理
控制對一個對象的訪問,如果需要,可以給不同的用戶提供不同級別的使用權限。
總結
以上是生活随笔為你收集整理的代理模式——静态代理,动态代理(JDK代理和CGLib代理)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: arthas-boot.jar 工具的简
- 下一篇: 适配器模式——类适配器模式,对象适配器模