java代理模式(jdk proxy)
什么是代理
舉個栗子
比如有一家美國的大學(xué),可以對全世界招生。但是對于家長來說,家長不能直接自己去找學(xué)校,家長沒有能力去直接訪問學(xué)校,或者說,美國學(xué)校不接受個人來訪,那么此時就需要一個留學(xué)中介來幫助這家美國學(xué)校招
生,中介就是學(xué)校的代理。中介和學(xué)校要做的事情是一致:招生。對于家長來說,學(xué)校就是目標(biāo),留學(xué)中介就是代理。日常生活中,有許多代理的例子,比如:代購,房產(chǎn)中介,各種中介,換ip,商家廠家和買家。在開發(fā)中
也有同樣的情況,比如,你有a類, 本來是調(diào)用c類的方法, 完成某個功能。 但是c不讓a調(diào)用。 a -----不能調(diào)用 c的方法。在a 和 c 直接 創(chuàng)建一個 b 代理, c讓b訪問。 a --訪問b---訪問c。
原來的訪問關(guān)系
通過代理的訪問關(guān)系
什么是代理模式
百度百科
代理模式是指,為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶類和目標(biāo)對象之間起到中介的作用。
換句話說,使用代理對象,是為了在不修改目標(biāo)對象的基礎(chǔ)上,增強(qiáng)主業(yè)務(wù)邏輯??蛻纛愓嬲南胍L問的對象是目標(biāo)對象,但客戶類真正可以訪問的對象是代理對象??蛻纛悓δ繕?biāo)對象的訪問是通過訪問代理對象來實現(xiàn)
的。當(dāng)然,代理類與目標(biāo)類要實現(xiàn)同一個接口。
實現(xiàn)代理的方式
靜態(tài)代理
靜態(tài)代理是指,代理類在程序運行前就已經(jīng)定義好.java 源文件,其與目標(biāo)類的關(guān)系在程序運行前就已經(jīng)確立。在程序運行前代理類已經(jīng)編譯為.class 文件。
舉一個靜態(tài)代理的例子
需求:用戶需要購買 u 盤,u 盤廠家不單獨接待零散購買,廠家規(guī)定一次最少購買 1000個以上,用戶可以通過淘寶的代理商,或者微商哪里進(jìn)行購買。淘寶上的商品,微商都是 u 盤工廠的代理商, 他們代理對 u 盤的銷售業(yè)
務(wù)。用戶購買-------代理商(淘寶,微商)----- u 盤廠家(金士頓,閃迪等不同的廠家)
1、定義業(yè)務(wù)接口
定義業(yè)務(wù)接口UsbSell(目標(biāo)接口),其中含有抽象方法sell(int amout);sell是目標(biāo)方法
public interface UsbSell { /** * 表示功能的,廠家和商家都要完成的功能 * @param amount * @return */ float sellUsb(int amount); }
2、定義接口的實現(xiàn)類
目標(biāo)類UsbKingFactory金士頓U盤,該類實現(xiàn)了接口
import school.xauat.service.UsbSell; public class UsbKingFactory implements UsbSell { @Override /** * 定義金士頓廠家的銷售價格 */ public float sellUsb(int account) { return 75.0f; } }
3、定義代理
TaoBao就是一個代理類,代理廠家銷售U盤
import school.xauat.factory.UsbKingFactory; import school.xauat.service.UsbSell; public class Taobao implements UsbSell { //聲明 商家代理的廠家具體是哪一家 private UsbSell factory=new UsbKingFactory(); @Override /** * 實現(xiàn)銷售U盤的功能 */ public float sellUsb(int account) { float price=factory.sellUsb(account); //代理增強(qiáng)功能 price+=25; return price; } }
WeiShang也是一個代理類代理廠家銷售U盤
import school.xauat.factory.UsbKingFactory; import school.xauat.service.UsbSell; public class Weishang implements UsbSell { //聲明 商家代理的廠家具體是哪一家 private UsbSell factory=new UsbKingFactory(); @Override public float sellUsb(int amount) { float price=factory.sellUsb(amount); //代理增強(qiáng)功能 price+=15; return price; } }
4、客戶端調(diào)用者,購買商品類
客戶端可以通過Taobao和WeiShang兩個代理來購買U盤
import school.xauat.business.Taobao; import school.xauat.business.Weishang; public class ShopMain { public static void main(String[] args) { Taobao taoBao=new Taobao(); float price=taoBao.sellUsb(1); System.out.println(price); Weishang weishang=new Weishang(); float price2=weishang.sellUsb(1); System.out.println(price2); } }
根據(jù)以上過程,分析靜態(tài)代理的優(yōu)缺點
優(yōu)點:實現(xiàn)簡單,易于理解
缺點:
代碼復(fù)雜,難于管理
代理類和目標(biāo)類實現(xiàn)了相同的接口,每個代理都需要實現(xiàn)目標(biāo)類的方法,這樣就出現(xiàn)了大量的代碼重復(fù)。如果接口增加一個方法,除了所有目標(biāo)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。
代理類依賴目標(biāo)類,代理類過多
代理類只服務(wù)于一種類型的目標(biāo)類,如果要服務(wù)多個類型。勢必要為每一種目標(biāo)類都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了,代理類數(shù)量過多。
動態(tài)代理
動態(tài)代理是指代理類對象在程序運行時由 JVM 根據(jù)反射機(jī)制動態(tài)生成的。動態(tài)代理不需要定義代理類的.java 源文件。
動態(tài)代理其實就是 jdk 運行期間,動態(tài)創(chuàng)建 class 字節(jié)碼并加載到 JVM。
動態(tài)代理的實現(xiàn)方式常用的有兩種:使用 JDK 動態(tài)代理(這里主要講),與通過 CGLIB 動態(tài)代理。
CGLIB代理
CGLIB(Code Generation Library)是一個開源項目。是一個強(qiáng)大的,高性能,高質(zhì)量的 Code 生成類庫,它可以在運行期擴(kuò)展 Java 類與實現(xiàn) Java 接口。它廣泛的被許多 AOP 的框架使用,例如 Spring AOP。使用 JDK 的
Proxy 實現(xiàn)代理,要求目標(biāo)類與代理類實現(xiàn)相同的接口。若目標(biāo)類不存在接口,則無法使用該方式實現(xiàn)。但對于無接口的類,要為其創(chuàng)建動態(tài)代理,就要使用 CGLIB 來實現(xiàn)。CGLIB 代理的生成原理是生成目標(biāo)類的子類,而
子類是增強(qiáng)過的,這個子類對象就是代理對象。所以,使用CGLIB 生成動態(tài)代理,要求目標(biāo)類必須能夠被繼承,即不能是 final 的類。cglib 經(jīng)常被應(yīng)用在框架中,例如 Spring ,Hibernate 等。Cglib 的代理效率高于 Jdk。對
于 cglib 一般的開發(fā)中并不使用。做了一個了解就可以。
JDK代理
jdk 動態(tài)代理是基于 Java 的反射機(jī)制實現(xiàn)的。使用 jdk 中接口和類實現(xiàn)代理對象的動態(tài)創(chuàng) 建。 Jdk 的動態(tài)要求目標(biāo)對象必須實現(xiàn)接口,這是 java 設(shè)計上的要求。 從 jdk1.3 以來,java 語言通過 java.lang.reflect 包提供三個類
支持代理模式 Proxy, Method 和 InovcationHandler。
InvocationHandler接口
InvocationHandler 接口叫做調(diào)用處理器,負(fù)責(zé)完調(diào)用目標(biāo)方法,并增強(qiáng)功能。通 過 代 理 對 象 執(zhí) 行 目 標(biāo) 接 口 中 的 方 法 , 會 把 方 法 的 調(diào) 用 分 派 給 調(diào) 用 處 理 器(InvocationHandler)的實現(xiàn)類,執(zhí)行實現(xiàn)類中的 i
nvoke()方法,我們需要把功能代理寫在 invoke()方法中 。
在 invoke 方法中可以截取對目標(biāo)方法的調(diào)用。在這里進(jìn)行功能增強(qiáng)。Java 的動態(tài)代理是建立在反射機(jī)制之上的。實現(xiàn)了 InvocationHandler 接口的類用于加強(qiáng)目標(biāo)類的主業(yè)務(wù)邏輯。這個接口中有一個方法 invoke(),具體加
強(qiáng)的代碼邏輯就是定義在該方法中的。通過代理對象執(zhí)行接口中的方法時,會自動調(diào)用 invoke()方法。
invoke()方法的介紹如下:
public Object invoke ( Object proxy, Method method, Object[] args)
proxy:代表生成的代理對象
method:代表目標(biāo)方法
args:代表目標(biāo)方法的參數(shù)
第一個參數(shù) proxy 是 jdk 在運行時賦值的,在方法中直接使用,第二個參數(shù)后面介紹,第三個參數(shù)是方法執(zhí)行的參數(shù), 這三個參數(shù)都是 jdk 運行時賦值的,無需程序員給出。
Method類
invoke()方法的第二個參數(shù)為 Method 類對象,該類有一個方法也叫 invoke(),可以調(diào)用目標(biāo)方法。這兩個 invoke()方法,雖然同名,但無關(guān)。
public Object invoke ( Object obj, Object... args)
obj
:表示目標(biāo)對象
args
:表示目標(biāo)方法參數(shù),就是其上一層 invoke 方法的第三個參數(shù)
該方法的作用是:調(diào)用執(zhí)行 obj 對象所屬類的方法,這個方法由其調(diào)用者 Method 對象確定。
在代碼中,一般的寫法為
method.invoke(target, args);
其中,method 為上一層 invoke 方法的第二個參數(shù)。這樣,即可調(diào)用了目標(biāo)類的目標(biāo)方法。
Proxy類
通 過 JDK 的 java.lang.reflect.Proxy 類 實 現(xiàn) 動 態(tài) 代 理 , 會 使 用 其 靜 態(tài) 方 法newProxyInstance(),依據(jù)目標(biāo)對象、業(yè)務(wù)接口及調(diào)用處理器三者,自動生成一個動態(tài)代理對象。
public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
loader:目標(biāo)類的類加載器,通過目標(biāo)對象的反射可獲取
interfaces:目標(biāo)類實現(xiàn)的接口數(shù)組,通過目標(biāo)對象的反射可獲取
handler:調(diào)用處理器。
jdk動態(tài)代理的實現(xiàn)步驟
jdk 動態(tài)代理是代理模式的一種實現(xiàn)方式,其只能代理接口。
實現(xiàn)步驟
1、新建一個接口,作為目標(biāo)接口
2、為接口創(chuàng)建一個實現(xiàn)類,是目標(biāo)類
3、創(chuàng)建類實現(xiàn) java.lang.reflect.InvocationHandler 接口,調(diào)用目標(biāo)方法并增加其他功能代碼
4、創(chuàng)建動態(tài)代理對象,使用 Proxy.newProxyInstance()方法,并把返回值強(qiáng)制轉(zhuǎn)為接口類型。
舉例
1、創(chuàng)建目標(biāo)接口,定義目標(biāo)接口功能
2、為接口創(chuàng)建實現(xiàn)類
以上兩步同靜態(tài)代理
3、創(chuàng)建InvocationHandler接口的實現(xiàn)類,調(diào)用目標(biāo)方法和增加其他代碼功能
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MySellHandler implements InvocationHandler { //目標(biāo)對象 private Object target=null; public MySellHandler(Object target){ this.target=target; } @Override /** * 實現(xiàn)InvocationHandler接口的實現(xiàn)類,在invoke方法中完成代理類的功能 * -調(diào)用目標(biāo)方法 * -功能增強(qiáng) */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; result=method.invoke(target,args); //功能增強(qiáng) //這里為了簡化,我們將功能增強(qiáng)定義為加價25元 if (result!=null){ float price=(float)result; price=price+25; result=price; } return result; } }
這里的target對象相當(dāng)于靜態(tài)代理中的TaoBao和WeiShang
4、模擬客戶購買U盤,使用proxy.newProxyInstance創(chuàng)建Proxy代理對象并且使返回值為目標(biāo)接口類型
import school.xauat.factory.UsbKingFactory; import school.xauat.handler.MySellHandler; import school.xauat.service.UsbSell; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class MainShop { public static void main(String[] args) throws Exception { //創(chuàng)建目標(biāo)對象 Class c=UsbKingFactory.class; Object obj=c.newInstance(); //獲得目標(biāo)類的類加載器 ClassLoader loader =UsbKingFactory.class.getClassLoader(); //獲取目標(biāo)類實現(xiàn)的接口數(shù)組 Class<?>[]interfaces=obj.getClass().getInterfaces(); //創(chuàng)建InvocationHandler對象 InvocationHandler handler=new MySellHandler(obj); //創(chuàng)建代理對象 UsbSell proxy=(UsbSell) Proxy.newProxyInstance(loader,interfaces,handler); //通過這個代理執(zhí)行方法 float price=proxy.sell(1); System.out.println(price); } }
靜態(tài)代理
動態(tài)代理
UML圖
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SpringBoot實現(xiàn)動態(tài)插拔的AOP的完整案例
在現(xiàn)代軟件開發(fā)中,面向切面編程(AOP) 是一種非常重要的技術(shù),能夠有效實現(xiàn)日志記錄、安全控制、性能監(jiān)控等橫切關(guān)注點的分離,在傳統(tǒng)的 AOP 實現(xiàn)中,切面邏輯往往是固定的,難以動態(tài)調(diào)整,本文將詳細(xì)探討如何利用 Spring Boot 實現(xiàn)動態(tài)插拔的 AOP,需要的朋友可以參考下2025-01-01spring boot 實現(xiàn)配置多個DispatcherServlet最簡單方式
這篇文章主要介紹了spring boot 實現(xiàn)配置多個DispatcherServlet最簡單方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01SpringDataJpa的使用之一對一、一對多、多對多?關(guān)系映射問題
這篇文章主要介紹了SpringDataJpa的使用?--?一對一、一對多、多對多關(guān)系映射,本文主要講述?@OneToOne、@OneToMany、@ManyToOne、@ManyToMany?這四個關(guān)系映射注解的使用,以及其對應(yīng)的級聯(lián)關(guān)系,需要的朋友可以參考下2022-07-07Spring Boot配置特定屬性spring.profiles的方法
這篇文章主要介紹了Spring Boot配置特定屬性spring.profiles的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11使用Spring Boot Mybatis 搞反向工程的步驟
這篇文章主要介紹了使用Spring Boot Mybatis 搞反向工程的步驟,幫助大家更好的理解和使用spring boot框架,感興趣的朋友可以了解下2021-01-01Java中JVM的雙親委派、內(nèi)存溢出、垃圾回收和調(diào)優(yōu)詳解
這篇文章主要介紹了Java中JVM的雙親委派、內(nèi)存溢出、垃圾回收和調(diào)優(yōu)詳解,類加載器是Java虛擬機(jī)(JVM)的一個重要組成部分,它的主要作用是將類的字節(jié)碼加載到內(nèi)存中,并生成對應(yīng)的Class對象,需要的朋友可以參考下2023-07-07