Java代理模式及動(dòng)態(tài)代理使用及說(shuō)明
Java的動(dòng)態(tài)代理在實(shí)踐中有著廣泛的使用場(chǎng)景,比如最場(chǎng)景的Spring AOP、Java注解的獲取、日志、用戶(hù)鑒權(quán)等。
本篇文章帶大家了解一下代理模式、靜態(tài)代理以及基于JDK原生動(dòng)態(tài)代理。
代理模式
無(wú)論學(xué)習(xí)靜態(tài)代理或動(dòng)態(tài)代理,我們都要先了解一下代理模式。
先看百度百科的定義:
- 代理模式的定義:為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪(fǎng)問(wèn)。
- 在某些情況下,一個(gè)對(duì)象不適合或者不能直接引用另一個(gè)對(duì)象,而代理對(duì)象可以在客戶(hù)端和目標(biāo)對(duì)象之間起到中介的作用。
直接看定義可能有些難以理解,我們就以生活中具體的實(shí)例來(lái)說(shuō)明一下。
我們都去過(guò)超市購(gòu)買(mǎi)過(guò)物品,超市從廠(chǎng)商那里購(gòu)買(mǎi)貨物之后出售給我們,我們通常并不知道貨物從哪里經(jīng)過(guò)多少流程才到超市。
在這個(gè)過(guò)程中,等于是廠(chǎng)商“委托”超市出售貨物,對(duì)我們來(lái)說(shuō)是廠(chǎng)商(真實(shí)對(duì)象)是不可見(jiàn)的。而超市(代理對(duì)象)呢,作為廠(chǎng)商的“代理者”來(lái)與我們進(jìn)行交互。
同時(shí),超市還可以根據(jù)具體的銷(xiāo)售情況來(lái)進(jìn)行折扣等處理,來(lái)豐富被代理對(duì)象的功能。
通過(guò)代理模式,我們可以做到兩點(diǎn):
- 1、隱藏委托類(lèi)的具體實(shí)現(xiàn)。
- 2、實(shí)現(xiàn)客戶(hù)與委托類(lèi)的解耦,在不改變委托類(lèi)代碼的情況下添加一些額外的功能(日志、權(quán)限)等。
代理模式角色定義
在上述的過(guò)程中在編程的過(guò)程中我們可以定義為三類(lèi)對(duì)象:
- Subject(抽象主題角色):定義代理類(lèi)和真實(shí)主題的公共對(duì)外方法,也是代理類(lèi)代理真實(shí)主題的方法。比如:廣告、出售等。
- RealSubject(真實(shí)主題角色):真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類(lèi)。比如實(shí)現(xiàn)了廣告、出售等方法的廠(chǎng)家(Vendor)。
- Proxy(代理主題角色):用來(lái)代理和封裝真實(shí)主題。比如,同樣實(shí)現(xiàn)了廣告、出售等方法的超時(shí)(Shop)。
以上三個(gè)角色對(duì)應(yīng)的類(lèi)圖如下:

靜態(tài)代理實(shí)例
靜態(tài)代理是指代理類(lèi)在程序運(yùn)行前就已經(jīng)存在,這種情況下的代理類(lèi)通常都是我們?cè)贘ava代碼中定義的。
下面我們就以具體的實(shí)例來(lái)演示一下靜態(tài)代理。
首先定義一組接口Sell,用來(lái)提供廣告和銷(xiāo)售等功能。然后提供Vendor類(lèi)(廠(chǎng)商,被代理對(duì)象)和Shop(超市,代理類(lèi)),它們分別實(shí)現(xiàn)了Sell接口。
Sell接口定義如下:
/**
* 委托類(lèi)和代理類(lèi)都實(shí)現(xiàn)了Sell接口
* @author sec
* @version 1.0
* @date 2020/3/21 9:30 AM
**/
public interface Sell {
/**
* 出售
*/
void sell();
/**
* 廣告
*/
void ad();
}Vendor類(lèi)定義如下:
/**
* 供應(yīng)商
* @author sec
* @version 1.0
* @date 2020/3/21 9:30 AM
**/
public class Vendor implements Sell{
@Override
public void sell() {
System.out.println("Shop sell goods");
}
@Override
public void ad() {
System.out.println("Shop advert goods");
}
}Shop類(lèi)定義如下:
/**
* 超市,代理類(lèi)
* @author sec
* @version 1.0
* @date 2020/3/21 9:30 AM
**/
public class Shop implements Sell{
private Sell sell;
public Shop(Sell sell){
this.sell = sell;
}
@Override
public void sell() {
System.out.println("代理類(lèi)Shop,處理sell");
sell.sell();
}
@Override
public void ad() {
System.out.println("代理類(lèi)Shop,處理ad");
sell.ad();
}
}其中代理類(lèi)Shop通過(guò)聚合的方式持有了被代理類(lèi)Vendor的引用,并在對(duì)應(yīng)的方法中調(diào)用Vendor對(duì)應(yīng)的方法。在Shop類(lèi)中我們可以新增一些額外的處理,比如篩選購(gòu)買(mǎi)用戶(hù)、記錄日志等操作。
下面看看在客戶(hù)端中如何使用代理類(lèi)。
/**
* 靜態(tài)代理類(lèi)測(cè)試方法
* @author sec
* @version 1.0
* @date 2020/3/21 9:33 AM
**/
public class StaticProxy {
public static void main(String[] args) {
// 供應(yīng)商---被代理類(lèi)
Vendor vendor = new Vendor();
// 創(chuàng)建供應(yīng)商的代理類(lèi)Shop
Sell sell = new Shop(vendor);
// 客戶(hù)端使用時(shí)面向的是代理類(lèi)Shop。
sell.ad();
sell.sell();
}
}在上述代碼中,針對(duì)客戶(hù)看到的是Sell接口提供了功能,而功能又是由Shop提供的。我們可以在Shop中修改或新增一些內(nèi)容,而不影響被代理類(lèi)Vendor。
靜態(tài)代理的缺點(diǎn)
靜態(tài)代理實(shí)現(xiàn)簡(jiǎn)單且不侵入原代碼,但當(dāng)場(chǎng)景復(fù)雜時(shí),靜態(tài)代理會(huì)有以下缺點(diǎn):
1、當(dāng)需要代理多個(gè)類(lèi)時(shí),代理對(duì)象要實(shí)現(xiàn)與目標(biāo)對(duì)象一致的接口。要么,只維護(hù)一個(gè)代理類(lèi)來(lái)實(shí)現(xiàn)多個(gè)接口,但這樣會(huì)導(dǎo)致代理類(lèi)過(guò)于龐大。要么,新建多個(gè)代理類(lèi),但這樣會(huì)產(chǎn)生過(guò)多的代理類(lèi)。
2、當(dāng)接口需要增加、刪除、修改方法時(shí),目標(biāo)對(duì)象與代理類(lèi)都要同時(shí)修改,不易維護(hù)。
于是,動(dòng)態(tài)代理便派上用場(chǎng)了。
動(dòng)態(tài)代理
動(dòng)態(tài)代理是指代理類(lèi)在程序運(yùn)行時(shí)進(jìn)行創(chuàng)建的代理方式。這種情況下,代理類(lèi)并不是在Java代碼中定義的,而是在運(yùn)行時(shí)根據(jù)Java代碼中的“指示”動(dòng)態(tài)生成的。
相比于靜態(tài)代理,動(dòng)態(tài)代理的優(yōu)勢(shì)在于可以很方便的對(duì)代理類(lèi)的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個(gè)代理類(lèi)的函數(shù)。
基于JDK原生動(dòng)態(tài)代理實(shí)現(xiàn)
實(shí)現(xiàn)動(dòng)態(tài)代理通常有兩種方式:JDK原生動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。這里,我們以JDK原生動(dòng)態(tài)代理為例來(lái)進(jìn)行講解。
JDK動(dòng)態(tài)代理主要涉及兩個(gè)類(lèi):java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
InvocationHandler接口定義了如下方法:
/**
* 調(diào)用處理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}顧名思義,實(shí)現(xiàn)了該接口的中介類(lèi)用做“調(diào)用處理器”。當(dāng)調(diào)用代理類(lèi)對(duì)象的方法時(shí),這個(gè)“調(diào)用”會(huì)轉(zhuǎn)送到invoke方法中,代理類(lèi)對(duì)象作為proxy參數(shù)傳入,參數(shù)method標(biāo)識(shí)了具體調(diào)用的是代理類(lèi)的哪個(gè)方法,args為該方法的參數(shù)。這樣對(duì)代理類(lèi)中的所有方法的調(diào)用都會(huì)變?yōu)閷?duì)invoke的調(diào)用,可以在invoke方法中添加統(tǒng)一的處理邏輯(也可以根據(jù)method參數(shù)對(duì)不同的代理類(lèi)方法做不同的處理)。
Proxy類(lèi)用于獲取指定代理對(duì)象所關(guān)聯(lián)的調(diào)用處理器。
下面以添加日志為例來(lái)演示一下動(dòng)態(tài)代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的對(duì)象,實(shí)際的方法執(zhí)行者
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 調(diào)用 target 的 method 方法
after();
return result; // 返回方法的執(zhí)行結(jié)果
}
// 調(diào)用invoke方法之前執(zhí)行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 調(diào)用invoke方法之后執(zhí)行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}客戶(hù)端編寫(xiě)程序使用動(dòng)態(tài)代理代碼如下:
import java.lang.reflect.Proxy;
/**
* 動(dòng)態(tài)代理測(cè)試
*
* @author sec
* @version 1.0
* @date 2020/3/21 10:40 AM
**/
public class DynamicProxyMain {
public static void main(String[] args) {
// 創(chuàng)建中介類(lèi)實(shí)例
LogHandler logHandler = new LogHandler(new Vendor());
// 設(shè)置該變量可以保存動(dòng)態(tài)代理類(lèi),默認(rèn)名稱(chēng)$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 獲取代理類(lèi)實(shí)例Sell
Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));
// 通過(guò)代理類(lèi)對(duì)象調(diào)用代理類(lèi)方法,實(shí)際上會(huì)轉(zhuǎn)到invoke方法調(diào)用
sell.sell();
sell.ad();
}
}執(zhí)行之后,打印日志如下:
調(diào)用方法sell之【前】的日志處理
Shop sell goods
調(diào)用方法sell之【后】的日志處理
調(diào)用方法ad之【前】的日志處理
Shop advert goods
調(diào)用方法ad之【后】的日志處理
經(jīng)過(guò)上述驗(yàn)證,我們發(fā)現(xiàn)已經(jīng)成功為我們的被代理類(lèi)統(tǒng)一添加了執(zhí)行方法之前和執(zhí)行方法之后的日志。
在上述實(shí)例中為了看一下生成的動(dòng)態(tài)代理類(lèi)的代碼,我們添加了下面的屬性設(shè)置(在生產(chǎn)環(huán)境中需要去掉該屬性)。
// 設(shè)置該變量可以保存動(dòng)態(tài)代理類(lèi),默認(rèn)名稱(chēng)$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");那么,我們可以執(zhí)行main方法之后,還生成了一個(gè)名字為$Proxy0.class類(lèi)文件。
通過(guò)反編譯可看到如下的代碼:
package com.sun.proxy;
import com.choupangxia.proxy.Sell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Sell {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void ad() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sell() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}可以看到$Proxy0(代理類(lèi))繼承了Proxy類(lèi),并且實(shí)現(xiàn)了被代理的所有接口,以及equals、hashCode、toString等方法。
由于動(dòng)態(tài)代理類(lèi)繼承了Proxy類(lèi),所以每個(gè)代理類(lèi)都會(huì)關(guān)聯(lián)一個(gè)InvocationHandler方法調(diào)用處理器。
類(lèi)和所有方法都被public final修飾,所以代理類(lèi)只可被使用,不可以再被繼承。
每個(gè)方法都有一個(gè)Method對(duì)象來(lái)描述,Method對(duì)象在static靜態(tài)代碼塊中創(chuàng)建,以“m+數(shù)字”的格式命名。
調(diào)用方法的時(shí)候通過(guò)super.h.invoke(this,m1,(Object[])null);調(diào)用。其中的super.h.invoke實(shí)際上是在創(chuàng)建代理的時(shí)候傳遞給Proxy.newProxyInstance的LogHandler對(duì)象,它繼承InvocationHandler類(lèi),負(fù)責(zé)實(shí)際的調(diào)用處理邏輯。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java之synchronized(含與ReentrantLock的區(qū)別解讀)
文章主要介紹了`synchronized`和`ReentrantLock`的區(qū)別,包括它們的實(shí)現(xiàn)原理、公平性、靈活性、可中斷性等方面,同時(shí),文章詳細(xì)解釋了`synchronized`的使用方法,包括修飾實(shí)例方法、靜態(tài)方法和代碼塊的情況,以及如何分析代碼是否互斥和可重入性2025-01-01
Java8新特性O(shè)ptional類(lèi)處理空值判斷回避空指針異常應(yīng)用
這篇文章主要介紹了Java8新特性O(shè)ptional類(lèi)處理空值判斷回避空指針異常應(yīng)用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
Jedis操作Redis數(shù)據(jù)庫(kù)的方法
這篇文章主要為大家詳細(xì)介紹了Jedis操作Redis數(shù)據(jù)庫(kù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04
Java在Map轉(zhuǎn)Json字符串時(shí)出現(xiàn)"\"轉(zhuǎn)義字符的解決辦法
當(dāng)一個(gè)Map被轉(zhuǎn)成Json字符串后,被添加到另一個(gè)Map中,會(huì)出現(xiàn)被加上“\”轉(zhuǎn)義字符的情況,這個(gè)時(shí)候該如何解決呢,下面就來(lái)和小編一起了解一下2023-07-07
SpringBoot多數(shù)據(jù)源讀寫(xiě)分離的自定義配置問(wèn)題及解決方法
這篇文章主要介紹了SpringBoot多數(shù)據(jù)源讀寫(xiě)分離的自定義配置,我們可以通過(guò)自定義配置數(shù)據(jù)庫(kù)配置類(lèi)來(lái)解決這個(gè)問(wèn)題,方式有很多,不同的業(yè)務(wù)采用的方式也不同,下面我簡(jiǎn)單的介紹我們項(xiàng)目的使用的方法2022-06-06
Spring AOP切點(diǎn)表達(dá)式使用及說(shuō)明
這篇文章主要介紹了Spring AOP切點(diǎn)表達(dá)式使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
使用IDEA向Gitee提交SpringBoot項(xiàng)目進(jìn)行遠(yuǎn)程管理
本文主要介紹了使用IDEA向Gitee提交SpringBoot項(xiàng)目進(jìn)行遠(yuǎn)程管理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01

