代理模式
代理模式是指冀膝,為其他對象提供一種代理以控制對這個對象的訪問唁奢。在某些情況下,一個對象不適合或者不能直接引用另一個對象窝剖,而代理對象可以在客戶類和目標對象之間起到中介的作用麻掸。
換句話說,使用代理對象赐纱,是為了在不修改目標對象的基礎上脊奋,增強主業(yè)務邏輯。
客戶類真正的想要訪問的對象是目標對象疙描,但客戶類真正可以訪問的對象是代理對象诚隙。客戶類對目標對象的訪問是通過訪問代理對象來實現的久又。當然地消,代理類與目標類要實現同一個接口脉执。
例如: 有 A,B迅细,C 三個類嗦随, A 原來可以調用 C 類的方法, 現在因為某種原因 C 類不允許A 類調用其方法砂吞,但 B 類可以調用 C 類的方法蜻直。A 類通過 B 類調用 C 類的方法呼巷。這里 B 是 C的代理王悍。 A 通過代理 B 訪問 C.
原來的訪問關系:
通過代理的訪問關系:
Window 系統(tǒng)的快捷方式也是一種代理模式〖铮快捷方式代理的是真實的程序,雙擊快捷方式是啟動它代表的程序养渴。
代理模式作用
A厚脉、控制訪問
B霞溪、 增強功能
代理模式分類
可以將代理分為兩類:靜態(tài)代理與動態(tài)代理
代理的實現方式
靜態(tài)代理和動態(tài)代理
需求
需求:用戶需要購買 u 盤鸯匹,u 盤廠家不單獨接待零散購買殴蓬,廠家規(guī)定一次最少購買 1000個以上染厅,用戶可以通過淘寶的代理商肖粮,或者微商哪里進行購買行施。
淘寶上的商品蛾号,微商都是 u 盤工廠的代理商须教, 他們代理對 u 盤的銷售業(yè)務轻腺。
用戶購買-------代理商(淘寶贬养,微商)----- u 廠家(金士頓,閃迪等不同的廠家)
設計這個業(yè)務需要的類:
- 商家和廠家都是提供 sell 購買 u 盤的方法儿礼。定義購買 u 盤的接口 UsbSell
- 金士頓(King)對購買 1 千以上的價格是 85, 3 千以上是 80, 5 千以上是 75蚊夫。 單個 120元知纷。定義 UsbKingFactory 類琅轧,實現 UsbSell
- 閃迪(San)對購買 1 千以上的價格是 82, 3 千以上是 78, 5 千以上是 72。 單個 120 元睹酌。定義 UsbSanFactory 類忍疾,實現 UsbSell
- 定義淘寶的代理商 TaoBao 卤妒,實現 UsbSell
- 定義微商的代理商 WeiShang, 實現 UsbSell
- 定義測試類则披,測試通過淘寶士复, 微商購買 u 盤
靜態(tài)代理
靜態(tài)代理是指阱洪,代理類在程序運行前就已經定義好.java 源文件冗荸,其與目標類的關系在程序運行前就已經確立蚌本。在程序運行前代理類已經編譯為.class 文件。
靜態(tài)代理
在 idea 中創(chuàng)建 java 工程嵌莉,
工程名稱:ch01-staticproxy
(1) 定義業(yè)務接口
定義業(yè)務接口 UsbSell(目標接口)烦秩,其中含有抽象方法 sell(int amount), sell 是目標方法抛寝。
(2) 定義接口實現類
目標類 UsbKingFactory(金士頓 u 盤),該類實現了業(yè)務接口钻趋。
(3) 代理商 TaoBao
TaoBao 就是一個代理類较沪, 代理廠家銷售 u 盤
(4) 代理商 WeiShang
WeiShang 就是一個代理類, 代理廠家銷售 u 盤
(5) 客戶端調用者,購買商品類
使用代理的訪問關系圖:
靜態(tài)代理的缺點
(1) 代碼復雜茬射,難于管理
代理類和目標類實現了相同的接口躲株,每個代理都需要實現目標類的方法,這樣就出現了大量的代碼重復望浩。如果接口增加一個方法磨德,除了所有目標類需要實現這個方法外,所有代理類也需要實現此方法啦吧。增加了代碼維護的復雜度琳水。
(2) 代理類依賴目標類,代理類過多
代理類只服務于一種類型的目標類私沮,如果要服務多個類型造垛。勢必要為每一種目標類都進行代理筋搏,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了,代理類數量過多。
動態(tài)代理
動態(tài)代理是指代理類對象在程序運行時由 JVM 根據反射機制動態(tài)生成的建丧。動態(tài)代理不需要定義代理類的.java 源文件翎朱。
動態(tài)代理其實就是 jdk 運行期間,動態(tài)創(chuàng)建 class 字節(jié)碼并加載到 JVM凛忿。
動態(tài)代理的實現方式常用的有兩種:使用 JDK 動態(tài)代理澈灼,與通過 CGLIB 動態(tài)代理。
jdk 的動態(tài)代理
jdk 動態(tài)代理是基于 Java 的反射機制實現的店溢。使用 jdk 中接口和類實現代理對象的動態(tài)創(chuàng)建叁熔。
Jdk 的動態(tài)要求目標對象必須實現接口,這是 java 設計上的要求床牧。
從 jdk1.3 以來荣回,java 語言通過 java.lang.reflect 包提供三個類支持代理模式 Proxy, Method和 InovcationHandler。
(1) InvocationHandler 接口
InvocationHandler 接口叫做調用處理器戈咳,負責完調用目標方法糯累,并增強功能胖秒。
通 過 代 理 對 象 執(zhí) 行 目 標 接 口 中 的 方 法 判导, 會 把 方 法 的 調 用 分 派 給 調 用 處 理 器(InvocationHandler)的實現類,執(zhí)行實現類中的 invoke()方法,我們需要把功能代理寫在 invoke()方法中 疹娶。
接口中只有一個方法:
在 invoke 方法中可以截取對目標方法的調用移斩。在這里進行功能增強你稚。Java 的動態(tài)代理是建立在反射機制之上的鸡典。
實現了 InvocationHandler 接口的類用于加強目標類的主業(yè)務邏輯症汹。這個接口中有一個方法 invoke()破婆,具體加強的代碼邏輯就是定義在該方法中的谤职。通過代理對象執(zhí)行接口中的方法時,會自動調用 invoke()方法。
invoke()方法的介紹如下:
public Object invoke ( Object proxy, Method method, Object[] args)
proxy:代表生成的代理對象
method:代表目標方法
args:代表目標方法的參數
第一個參數 proxy 是 jdk 在運行時賦值的撬码,在方法中直接使用驼鹅,第二個參數后面介紹钓辆,
第三個參數是方法執(zhí)行的參數壳咕, 這三個參數都是 jdk 運行時賦值的聂宾,無需程序員給出薪寓。
(2) Method 類
invoke()方法的第二個參數為 Method 類對象存崖,該類有一個方法也叫 invoke()供搀,可以調用目標方法。這兩個 invoke()方法万栅,雖然同名,但無關。
public Object invoke ( Object obj, Object... args)
obj:表示目標對象
args:表示目標方法參數个初,就是其上一層 invoke 方法的第三個參數
該方法的作用是:調用執(zhí)行 obj 對象所屬類的方法,這個方法由其調用者 Method 對象確定弄息。
在代碼中痊班,一般的寫法為
method.invoke(target, args);
其中,method 為上一層 invoke 方法的第二個參數摹量。這樣涤伐,即可調用了目標類的目標方法馒胆。
(3) Proxy 類
通 過 JDK 的 java.lang.reflect.Proxy 類 實 現 動 態(tài) 代 理 , 會 使 用 其 靜 態(tài) 方 法newProxyInstance()凝果,依據目標對象祝迂、業(yè)務接口及調用處理器三者,自動生成一個動態(tài)代理對象器净。
public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
loader:目標類的類加載器型雳,通過目標對象的反射可獲取
interfaces:目標類實現的接口數組,通過目標對象的反射可獲取
handler:調用處理器山害。
jdk 動態(tài)代理實現
jdk 動態(tài)代理是代理模式的一種實現方式纠俭,其只能代理接口。
實現步驟
1浪慌、新建一個接口冤荆,作為目標接口
2、為接口創(chuàng)建一個實現類权纤,是目標類
3钓简、創(chuàng)建類實現 java.lang.reflect.InvocationHandler 接口,調用目標方法并增加其他功能代碼
4汹想、創(chuàng)建動態(tài)代理對象外邓,使用 Proxy.newProxyInstance()方法,并把返回值強制轉為接口類型古掏。
idea 創(chuàng)建 java project
工程名稱:ch02-dynamicproxy
(1) 定義目標接口
(2) 定義目標接口實現類
(3) 定義調用處理程序
調用處理程序是實現了 InvocationHandler 的類损话,在 invoke 方法中增加業(yè)務功能。還需要創(chuàng)
建有參構造冗茸,參數是目標對象席镀。為的是完成對目標對象的方法調用。
(4) 創(chuàng)建動態(tài)代理對象
執(zhí)行流程:
類圖:
cgLib 代理
CGLIB(Code Generation Library)是一個開源項目夏漱。是一個強大的,高性能顶捷,高質量的 Code 生成類庫挂绰,它可以在運行期擴展 Java 類與實現 Java 接口。它廣泛的被許多 AOP 的框架使用服赎,例如 Spring AOP葵蒂。
使用 JDK 的 Proxy 實現代理,要求目標類與代理類實現相同的接口重虑。若目標類不存在接口践付,則無法使用該方式實現。
但對于無接口的類缺厉,要為其創(chuàng)建動態(tài)代理永高,就要使用 CGLIB 來實現隧土。CGLIB 代理的生成原理是生成目標類的子類,而子類是增強過的命爬,這個子類對象就是代理對象曹傀。所以,使用CGLIB 生成動態(tài)代理饲宛,要求目標類必須能夠被繼承皆愉,即不能是 final 的類。
cglib 經常被應用在框架中艇抠,例如 Spring 幕庐,Hibernate 等。Cglib 的代理效率高于 Jdk家淤。對于 cglib 一般的開發(fā)中并不使用翔脱。做了一個了解就可以。
總結
動態(tài)代理(理解): 基于反射機制媒鼓。
- 什么是動態(tài)代理 届吁?
使用jdk的反射機制,創(chuàng)建對象的能力绿鸣, 創(chuàng)建的是代理類的對象疚沐。 而不用你創(chuàng)建類文件。不用寫java文件潮模。
動態(tài):在程序執(zhí)行時亮蛔,調用jdk提供的方法才能創(chuàng)建代理類的對象。
jdk動態(tài)代理擎厢,必須有接口究流,目標類必須實現接口, 沒有接口時动遭,需要使用cglib動態(tài)代理
- 知道動態(tài)代理能做什么 芬探?
可以在不改變原來目標方法功能的前提下, 可以在代理中增強自己的功能代碼厘惦。
程序開發(fā)中的意思偷仿。
比如:你所在的項目中,有一個功能是其他人(公司的其它部門宵蕉,其它小組的人)寫好的酝静,你可以使用。
GoNong.class , GoNong gn = new GoNong(), gn.print();
你發(fā)現這個功能羡玛,現在還缺點别智, 不能完全滿足我項目的需要。 我需要在gn.print()執(zhí)行后稼稿,需要自己在增加代碼薄榛。
用代理實現 gn.print()調用時讳窟, 增加自己代碼, 而不用去改原來的 GoNong文件蛇数。
1.代理
代購挪钓, 中介,換ip耳舅,商家等等
比如有一家美國的大學碌上, 可以對全世界招生。 留學中介(代理)
留學中介(代理): 幫助這家美國的學校招生浦徊, 中介是學校的代理馏予, 中介是代替學校完成招生功能。
代理特點:
1) 中介和代理他們要做的事情是一致的: 招生盔性。
2)中介是學校代理霞丧, 學校是目標。
3)家長---中介(學校介紹冕香,辦入學手續(xù))----美國學校蛹尝。
4) 中介是代理,不能白干活悉尾,需要收取費用突那。
5) 代理不讓你訪問到目標。
為什么要找中介 构眯?
1) 中介是專業(yè)的愕难, 方便
2) 家長現在不能自己去找學校。 家長沒有能力訪問學校惫霸。 或者美國學校不接收個人來訪猫缭。
買東西都是商家賣, 商家是某個商品的代理壹店, 你個人買東西猜丹, 肯定不會讓你接觸到廠家的。
- 在開發(fā)中也會有這樣的情況茫打, 你有a類居触, 本來是調用c類的方法, 完成某個功能老赤。 但是c不讓a調用。
a -----不能調用 c的方法制市。
在a 和 c 直接 創(chuàng)建一個 b 代理抬旺, c讓b訪問。
a --訪問b---訪問c
實際的例子: 登錄祥楣,注冊有驗證碼开财, 驗證碼是手機短信汉柒。
中國移動, 聯通能發(fā)短信责鳍。
中國移動碾褂, 聯通能有子公司,或者關聯公司历葛,他們面向社會提供短信的發(fā)送功能
張三項目發(fā)送短信----子公司正塌,或者關聯公司-----中國移動, 聯通
3.使用代理模式的作用
1).功能增強: 在你原有的功能上恤溶,增加了額外的功能乓诽。 新增加的功能,叫做功能增強咒程。
2)控制訪問: 代理類不讓你訪問目標鸠天,例如商家不讓用戶訪問廠家。
4.實現代理的方式
1)靜態(tài)代理 :
a) 代理類是自己手工實現的帐姻,自己創(chuàng)建一個java類稠集,表示代理類。
b)同時你所要代理的目標類是確定的饥瓷。
特點:
1)實現簡單
2)容易理解剥纷。
缺點:
當你的項目中,目標類和代理類很多時候扛伍,有以下的缺點:
1)當目標類增加了筷畦, 代理類可能也需要成倍的增加。 代理類數量過多刺洒。
2) 當你的接口中功能增加了鳖宾, 或者修改了,會影響眾多的實現類逆航,廠家類鼎文,代理類都需要修改。影響比較多。
模擬一個用戶購買u盤的行為厨相。
用戶是客戶端類
商家:代理潜慎,代理某個品牌的u盤。
廠家:目標類撑帖。
三者的關系: 用戶(客戶端)---商家(代理)---廠家(目標)
商家和廠家都是賣u盤的,他們完成的功能是一致的澳眷,都是賣u盤胡嘿。
實現步驟:
1). 創(chuàng)建一個接口,定義賣u盤的方法钳踊, 表示你的廠家和商家做的事情衷敌。
2). 創(chuàng)建廠家類勿侯,實現1步驟的接口
3). 創(chuàng)建商家,就是代理缴罗,也需要實現1步驟中的接口助琐。
4). 創(chuàng)建客戶端類,調用商家的方法買一個u盤面氓。
代理類完成的功能:
1). 目標類中方法的調用
2). 功能增強
- 動態(tài)代理
在靜態(tài)代理中目標類很多時候兵钮,可以使用動態(tài)代理,避免靜態(tài)代理的缺點侧但。
動態(tài)代理中目標類即使很多矢空,
1)代理類數量可以很少,
2)當你修改了接口中的方法時禀横,不會影響代理類屁药。
動態(tài)代理: 在程序執(zhí)行過程中,使用jdk的反射機制柏锄,創(chuàng)建代理類對象酿箭, 并動態(tài)的指定要代理目標類。
換句話說: 動態(tài)代理是一種創(chuàng)建java對象的能力趾娃,讓你不用創(chuàng)建TaoBao類缭嫡,就能創(chuàng)建代理類對象。
在java中抬闷,要想創(chuàng)建對象:
1.創(chuàng)建類文件妇蛀, java文件編譯為class
2.使用構造方法,創(chuàng)建類的對象笤成。
動態(tài)代理的實現:
- jdk動態(tài)代理(理解): 使用java反射包中的類和接口實現動態(tài)代理的功能评架。
反射包 java.lang.reflect , 里面有三個類 : InvocationHandler , Method, Proxy.
- cglib動態(tài)代理(了解): cglib是第三方的工具庫, 創(chuàng)建代理對象炕泳。
cglib的原理是繼承纵诞, cglib通過繼承目標類,創(chuàng)建它的子類培遵,在子類中 重寫父類中同名的方法浙芙, 實現功能的修改。
因為cglib是繼承籽腕,重寫方法嗡呼,所以要求目標類不能是final的, 方法也不能是final的皇耗。
cglib的要求目標類比較寬松晤锥, 只要能繼承就可以了。cglib在很多的框架中使用廊宪, 比如 mybatis 矾瘾,spring框架中都有使用。
jdk動態(tài)代理:
反射箭启, Method類壕翩,表示方法。類中的方法傅寡。 通過Method可以執(zhí)行某個方法放妈。
jdk動態(tài)代理的實現
反射包 java.lang.reflect , 里面有三個類 : InvocationHandler , Method, Proxy.
1)InvocationHandler 接口(調用處理器):就一個方法invoke()
invoke():表示代理對象要執(zhí)行的功能代碼。你的代理類要完成的功能就寫在invoke()方法中荐操。
代理類完成的功能:
- 調用目標方法芜抒,執(zhí)行目標方法的功能
- 功能增強,在目標方法調用時托启,增加功能宅倒。
方法原型:
public Object invoke(Object proxy, Method method, Object[] args)
參數:
Object proxy:jdk創(chuàng)建的代理對象,無需賦值屯耸。
Method method:目標類中的方法拐迁,jdk提供method對象的
Object[] args:目標類中方法的參數, jdk提供的疗绣。
InvocationHandler 接口:表示你的代理要干什么线召。
怎么用:
1.創(chuàng)建類實現接口InvocationHandler
2.重寫invoke()方法, 把原來靜態(tài)代理中代理類要完成的功能多矮,寫在這缓淹。
2)Method類:表示方法的, 確切的說就是目標類中的方法塔逃。
作用:通過Method可以執(zhí)行某個目標類的方法讯壶,Method.invoke();
method.invoke(目標對象,方法的參數)
Object ret = method.invoke(service2, "李四");
說明: method.invoke()就是用來執(zhí)行目標方法的患雏,等同于靜態(tài)代理中的:
//向廠家發(fā)送訂單鹏溯,告訴廠家,我買了u盤淹仑,廠家發(fā)貨
float price = factory.sell(amount); //廠家的價格丙挽。
3)Proxy類:核心的對象,創(chuàng)建代理對象匀借。之前創(chuàng)建對象都是 new 類的構造方法()現在我們是使用Proxy類的方法颜阐,代替new的使用。
方法: 靜態(tài)方法 newProxyInstance()
作用是: 創(chuàng)建代理對象吓肋, 等同于靜態(tài)代理中的TaoBao taoBao = new TaoBao();
參數:
- ClassLoader loader 類加載器凳怨,負責向內存中加載對象的。 使用反射獲取對象的ClassLoader類a , a.getCalss().getClassLoader(), 目標對象的類加載器
- Class<?>[] interfaces: 接口, 目標對象實現的接口肤舞,也是反射獲取的紫新。
- InvocationHandler h : 我們自己寫的,代理類要完成的功能李剖。
返回值:就是代理對象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- 實現動態(tài)代理的步驟:
1). 創(chuàng)建接口芒率,定義目標類要完成的功能
2). 創(chuàng)建目標類實現接口
3). 創(chuàng)建InvocationHandler接口的實現類,在invoke方法中完成代理類的功能:
1.調用目標方法
2.增強功能
4.使用Proxy類的靜態(tài)方法篙顺,創(chuàng)建代理對象偶芍。 并把返回值轉為接口類型。