JDK動態(tài)代理(AOP)

代理模式

代理模式是指,為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶類和目標對象之間起到中介的作用。

換句話說,使用代理對象,是為了在不修改目標對象的基礎上,增強主業(yè)務邏輯。

客戶類真正的想要訪問的對象是目標對象,但客戶類真正可以訪問的對象是代理對象??蛻纛悓δ繕藢ο蟮脑L問是通過訪問代理對象來實現(xiàn)的。當然,代理類與目標類要實現(xiàn)同一個接口。

例如: 有 A,B,C 三個類, A 原來可以調(diào)用 C 類的方法, 現(xiàn)在因為某種原因 C 類不允許A 類調(diào)用其方法,但 B 類可以調(diào)用 C 類的方法。A 類通過 B 類調(diào)用 C 類的方法。這里 B 是 C的代理。 A 通過代理 B 訪問 C.

原來的訪問關系:

image.png

通過代理的訪問關系:

image.png

Window 系統(tǒng)的快捷方式也是一種代理模式。快捷方式代理的是真實的程序,雙擊快捷方式是啟動它代表的程序。

代理模式作用

A、控制訪問
B、 增強功能

代理模式分類

可以將代理分為兩類:靜態(tài)代理與動態(tài)代理

代理的實現(xiàn)方式

靜態(tài)代理和動態(tài)代理

需求

需求:用戶需要購買 u 盤,u 盤廠家不單獨接待零散購買,廠家規(guī)定一次最少購買 1000個以上,用戶可以通過淘寶的代理商,或者微商哪里進行購買。

淘寶上的商品,微商都是 u 盤工廠的代理商, 他們代理對 u 盤的銷售業(yè)務。

用戶購買-------代理商(淘寶,微商)----- u 廠家(金士頓,閃迪等不同的廠家)

設計這個業(yè)務需要的類:

  1. 商家和廠家都是提供 sell 購買 u 盤的方法。定義購買 u 盤的接口 UsbSell
  2. 金士頓(King)對購買 1 千以上的價格是 85, 3 千以上是 80, 5 千以上是 75。 單個 120元。定義 UsbKingFactory 類,實現(xiàn) UsbSell
  3. 閃迪(San)對購買 1 千以上的價格是 82, 3 千以上是 78, 5 千以上是 72。 單個 120 元。定義 UsbSanFactory 類,實現(xiàn) UsbSell
  4. 定義淘寶的代理商 TaoBao ,實現(xiàn) UsbSell
  5. 定義微商的代理商 WeiShang, 實現(xiàn) UsbSell
  6. 定義測試類,測試通過淘寶, 微商購買 u 盤

靜態(tài)代理

靜態(tài)代理是指,代理類在程序運行前就已經(jīng)定義好.java 源文件,其與目標類的關系在程序運行前就已經(jīng)確立。在程序運行前代理類已經(jīng)編譯為.class 文件。

靜態(tài)代理

在 idea 中創(chuàng)建 java 工程,
工程名稱:ch01-staticproxy

(1) 定義業(yè)務接口
定義業(yè)務接口 UsbSell(目標接口),其中含有抽象方法 sell(int amount), sell 是目標方法。

image.png

(2) 定義接口實現(xiàn)類
目標類 UsbKingFactory(金士頓 u 盤),該類實現(xiàn)了業(yè)務接口。

image.png

(3) 代理商 TaoBao
TaoBao 就是一個代理類, 代理廠家銷售 u 盤

image.png

(4) 代理商 WeiShang
WeiShang 就是一個代理類, 代理廠家銷售 u 盤

image.png

(5) 客戶端調(diào)用者,購買商品類

image.png

使用代理的訪問關系圖:

image.png

靜態(tài)代理的缺點

(1) 代碼復雜,難于管理
代理類和目標類實現(xiàn)了相同的接口,每個代理都需要實現(xiàn)目標類的方法,這樣就出現(xiàn)了大量的代碼重復。如果接口增加一個方法,除了所有目標類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護的復雜度。

(2) 代理類依賴目標類,代理類過多
代理類只服務于一種類型的目標類,如果要服務多個類型。勢必要為每一種目標類都進行代理,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了,代理類數(shù)量過多。

動態(tài)代理

動態(tài)代理是指代理類對象在程序運行時由 JVM 根據(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)代理。

jdk 的動態(tài)代理

jdk 動態(tài)代理是基于 Java 的反射機制實現(xiàn)的。使用 jdk 中接口和類實現(xiàn)代理對象的動態(tài)創(chuàng)建。

Jdk 的動態(tài)要求目標對象必須實現(xiàn)接口,這是 java 設計上的要求。

從 jdk1.3 以來,java 語言通過 java.lang.reflect 包提供三個類支持代理模式 Proxy, Method和 InovcationHandler。

(1) InvocationHandler 接口
InvocationHandler 接口叫做調(diào)用處理器,負責完調(diào)用目標方法,并增強功能。

通 過 代 理 對 象 執(zhí) 行 目 標 接 口 中 的 方 法 , 會 把 方 法 的 調(diào) 用 分 派 給 調(diào) 用 處 理 器(InvocationHandler)的實現(xiàn)類,執(zhí)行實現(xiàn)類中的 invoke()方法,我們需要把功能代理寫在 invoke()方法中 。

image.png

接口中只有一個方法:

image.png

在 invoke 方法中可以截取對目標方法的調(diào)用。在這里進行功能增強。Java 的動態(tài)代理是建立在反射機制之上的。

實現(xiàn)了 InvocationHandler 接口的類用于加強目標類的主業(yè)務邏輯。這個接口中有一個方法 invoke(),具體加強的代碼邏輯就是定義在該方法中的。通過代理對象執(zhí)行接口中的方法時,會自動調(diào)用 invoke()方法。

invoke()方法的介紹如下:

public Object invoke ( Object proxy, Method method, Object[] args)
proxy:代表生成的代理對象
method:代表目標方法
args:代表目標方法的參數(shù)

第一個參數(shù) proxy 是 jdk 在運行時賦值的,在方法中直接使用,第二個參數(shù)后面介紹,
第三個參數(shù)是方法執(zhí)行的參數(shù), 這三個參數(shù)都是 jdk 運行時賦值的,無需程序員給出。

(2) Method 類
invoke()方法的第二個參數(shù)為 Method 類對象,該類有一個方法也叫 invoke(),可以調(diào)用目標方法。這兩個 invoke()方法,雖然同名,但無關。

public Object invoke ( Object obj, Object... args)
obj:表示目標對象
args:表示目標方法參數(shù),就是其上一層 invoke 方法的第三個參數(shù)

該方法的作用是:調(diào)用執(zhí)行 obj 對象所屬類的方法,這個方法由其調(diào)用者 Method 對象確定。

在代碼中,一般的寫法為
method.invoke(target, args);

其中,method 為上一層 invoke 方法的第二個參數(shù)。這樣,即可調(diào)用了目標類的目標方法。

(3) Proxy 類
通 過 JDK 的 java.lang.reflect.Proxy 類 實 現(xiàn) 動 態(tài) 代 理 , 會 使 用 其 靜 態(tài) 方 法newProxyInstance(),依據(jù)目標對象、業(yè)務接口及調(diào)用處理器三者,自動生成一個動態(tài)代理對象。

public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)

loader:目標類的類加載器,通過目標對象的反射可獲取
interfaces:目標類實現(xiàn)的接口數(shù)組,通過目標對象的反射可獲取
handler:調(diào)用處理器。

jdk 動態(tài)代理實現(xiàn)

jdk 動態(tài)代理是代理模式的一種實現(xiàn)方式,其只能代理接口。

實現(xiàn)步驟
1、新建一個接口,作為目標接口
2、為接口創(chuàng)建一個實現(xiàn)類,是目標類
3、創(chuàng)建類實現(xiàn) java.lang.reflect.InvocationHandler 接口,調(diào)用目標方法并增加其他功能代碼
4、創(chuàng)建動態(tài)代理對象,使用 Proxy.newProxyInstance()方法,并把返回值強制轉(zhuǎn)為接口類型。

idea 創(chuàng)建 java project
工程名稱:ch02-dynamicproxy

(1) 定義目標接口

image.png

(2) 定義目標接口實現(xiàn)類

image.png

(3) 定義調(diào)用處理程序
調(diào)用處理程序是實現(xiàn)了 InvocationHandler 的類,在 invoke 方法中增加業(yè)務功能。還需要創(chuàng)
建有參構(gòu)造,參數(shù)是目標對象。為的是完成對目標對象的方法調(diào)用。

image.png

(4) 創(chuàng)建動態(tài)代理對象

image.png

執(zhí)行流程:

image.png

類圖:

image.png

cgLib 代理

CGLIB(Code Generation Library)是一個開源項目。是一個強大的,高性能,高質(zhì)量的 Code 生成類庫,它可以在運行期擴展 Java 類與實現(xiàn) Java 接口。它廣泛的被許多 AOP 的框架使用,例如 Spring AOP。

使用 JDK 的 Proxy 實現(xiàn)代理,要求目標類與代理類實現(xiàn)相同的接口。若目標類不存在接口,則無法使用該方式實現(xiàn)。

但對于無接口的類,要為其創(chuàng)建動態(tài)代理,就要使用 CGLIB 來實現(xiàn)。CGLIB 代理的生成原理是生成目標類的子類,而子類是增強過的,這個子類對象就是代理對象。所以,使用CGLIB 生成動態(tài)代理,要求目標類必須能夠被繼承,即不能是 final 的類。

cglib 經(jīng)常被應用在框架中,例如 Spring ,Hibernate 等。Cglib 的代理效率高于 Jdk。對于 cglib 一般的開發(fā)中并不使用。做了一個了解就可以。

總結(jié)

動態(tài)代理(理解): 基于反射機制。

  1. 什么是動態(tài)代理 ?
    使用jdk的反射機制,創(chuàng)建對象的能力, 創(chuàng)建的是代理類的對象。 而不用你創(chuàng)建類文件。不用寫java文件。

動態(tài):在程序執(zhí)行時,調(diào)用jdk提供的方法才能創(chuàng)建代理類的對象。

jdk動態(tài)代理,必須有接口,目標類必須實現(xiàn)接口, 沒有接口時,需要使用cglib動態(tài)代理

  1. 知道動態(tài)代理能做什么 ?
    可以在不改變原來目標方法功能的前提下, 可以在代理中增強自己的功能代碼。

程序開發(fā)中的意思。
比如:你所在的項目中,有一個功能是其他人(公司的其它部門,其它小組的人)寫好的,你可以使用。
GoNong.class , GoNong gn = new GoNong(), gn.print();

你發(fā)現(xiàn)這個功能,現(xiàn)在還缺點, 不能完全滿足我項目的需要。 我需要在gn.print()執(zhí)行后,需要自己在增加代碼。

用代理實現(xiàn) gn.print()調(diào)用時, 增加自己代碼, 而不用去改原來的 GoNong文件。


1.代理
代購, 中介,換ip,商家等等

比如有一家美國的大學, 可以對全世界招生。 留學中介(代理)

留學中介(代理): 幫助這家美國的學校招生, 中介是學校的代理, 中介是代替學校完成招生功能。

代理特點:
1) 中介和代理他們要做的事情是一致的: 招生。
2)中介是學校代理, 學校是目標。
3)家長---中介(學校介紹,辦入學手續(xù))----美國學校。
4) 中介是代理,不能白干活,需要收取費用。
5) 代理不讓你訪問到目標。

為什么要找中介 ?
1) 中介是專業(yè)的, 方便
2) 家長現(xiàn)在不能自己去找學校。 家長沒有能力訪問學校。 或者美國學校不接收個人來訪。

買東西都是商家賣, 商家是某個商品的代理, 你個人買東西, 肯定不會讓你接觸到廠家的。

  1. 在開發(fā)中也會有這樣的情況, 你有a類, 本來是調(diào)用c類的方法, 完成某個功能。 但是c不讓a調(diào)用。

a -----不能調(diào)用 c的方法。
在a 和 c 直接 創(chuàng)建一個 b 代理, c讓b訪問。
a --訪問b---訪問c

實際的例子: 登錄,注冊有驗證碼, 驗證碼是手機短信。

中國移動, 聯(lián)通能發(fā)短信。

中國移動, 聯(lián)通能有子公司,或者關聯(lián)公司,他們面向社會提供短信的發(fā)送功能

張三項目發(fā)送短信----子公司,或者關聯(lián)公司-----中國移動, 聯(lián)通

3.使用代理模式的作用
1).功能增強: 在你原有的功能上,增加了額外的功能。 新增加的功能,叫做功能增強。
2)控制訪問: 代理類不讓你訪問目標,例如商家不讓用戶訪問廠家。

4.實現(xiàn)代理的方式
1)靜態(tài)代理 :
a) 代理類是自己手工實現(xiàn)的,自己創(chuàng)建一個java類,表示代理類。
b)同時你所要代理的目標類是確定的。

特點:
1)實現(xiàn)簡單
2)容易理解。

缺點:
當你的項目中,目標類和代理類很多時候,有以下的缺點:
1)當目標類增加了, 代理類可能也需要成倍的增加。 代理類數(shù)量過多。
2) 當你的接口中功能增加了, 或者修改了,會影響眾多的實現(xiàn)類,廠家類,代理類都需要修改。影響比較多。

模擬一個用戶購買u盤的行為。
用戶是客戶端類
商家:代理,代理某個品牌的u盤。
廠家:目標類。

三者的關系: 用戶(客戶端)---商家(代理)---廠家(目標)

商家和廠家都是賣u盤的,他們完成的功能是一致的,都是賣u盤。

實現(xiàn)步驟:
1). 創(chuàng)建一個接口,定義賣u盤的方法, 表示你的廠家和商家做的事情。
2). 創(chuàng)建廠家類,實現(xiàn)1步驟的接口
3). 創(chuàng)建商家,就是代理,也需要實現(xiàn)1步驟中的接口。
4). 創(chuàng)建客戶端類,調(diào)用商家的方法買一個u盤。

代理類完成的功能:
1). 目標類中方法的調(diào)用
2). 功能增強

  1. 動態(tài)代理
    在靜態(tài)代理中目標類很多時候,可以使用動態(tài)代理,避免靜態(tài)代理的缺點。
    動態(tài)代理中目標類即使很多,
    1)代理類數(shù)量可以很少,
    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.使用構(gòu)造方法,創(chuàng)建類的對象。

動態(tài)代理的實現(xiàn):

  1. jdk動態(tài)代理(理解): 使用java反射包中的類和接口實現(xiàn)動態(tài)代理的功能。

反射包 java.lang.reflect , 里面有三個類 : InvocationHandler , Method, Proxy.

  1. cglib動態(tài)代理(了解): cglib是第三方的工具庫, 創(chuàng)建代理對象。

cglib的原理是繼承, cglib通過繼承目標類,創(chuàng)建它的子類,在子類中 重寫父類中同名的方法, 實現(xiàn)功能的修改。

因為cglib是繼承,重寫方法,所以要求目標類不能是final的, 方法也不能是final的。

cglib的要求目標類比較寬松, 只要能繼承就可以了。cglib在很多的框架中使用, 比如 mybatis ,spring框架中都有使用。

jdk動態(tài)代理:

  1. 反射, Method類,表示方法。類中的方法。 通過Method可以執(zhí)行某個方法。

  2. jdk動態(tài)代理的實現(xiàn)
    反射包 java.lang.reflect , 里面有三個類 : InvocationHandler , Method, Proxy.

1)InvocationHandler 接口(調(diào)用處理器):就一個方法invoke()

invoke():表示代理對象要執(zhí)行的功能代碼。你的代理類要完成的功能就寫在invoke()方法中。

代理類完成的功能:

  1. 調(diào)用目標方法,執(zhí)行目標方法的功能
  2. 功能增強,在目標方法調(diào)用時,增加功能。

方法原型:
public Object invoke(Object proxy, Method method, Object[] args)

參數(shù):
Object proxy:jdk創(chuàng)建的代理對象,無需賦值。
Method method:目標類中的方法,jdk提供method對象的
Object[] args:目標類中方法的參數(shù), jdk提供的。

InvocationHandler 接口:表示你的代理要干什么。
怎么用:
1.創(chuàng)建類實現(xiàn)接口InvocationHandler
2.重寫invoke()方法, 把原來靜態(tài)代理中代理類要完成的功能,寫在這。

2)Method類:表示方法的, 確切的說就是目標類中的方法。
作用:通過Method可以執(zhí)行某個目標類的方法,Method.invoke();
method.invoke(目標對象,方法的參數(shù))

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 類的構(gòu)造方法()現(xiàn)在我們是使用Proxy類的方法,代替new的使用。

方法: 靜態(tài)方法 newProxyInstance()
作用是: 創(chuàng)建代理對象, 等同于靜態(tài)代理中的TaoBao taoBao = new TaoBao();

參數(shù):

  1. ClassLoader loader 類加載器,負責向內(nèi)存中加載對象的。 使用反射獲取對象的ClassLoader類a , a.getCalss().getClassLoader(), 目標對象的類加載器
  2. Class<?>[] interfaces: 接口, 目標對象實現(xiàn)的接口,也是反射獲取的。
  3. InvocationHandler h : 我們自己寫的,代理類要完成的功能。

返回值:就是代理對象

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

  1. 實現(xiàn)動態(tài)代理的步驟:
    1). 創(chuàng)建接口,定義目標類要完成的功能
    2). 創(chuàng)建目標類實現(xiàn)接口
    3). 創(chuàng)建InvocationHandler接口的實現(xiàn)類,在invoke方法中完成代理類的功能:
    1.調(diào)用目標方法
    2.增強功能

4.使用Proxy類的靜態(tài)方法,創(chuàng)建代理對象。 并把返回值轉(zhuǎn)為接口類型。

動態(tài)代理.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容