1. 簡介

代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
比如:我們在調用底層框架方法時候,需要在調用方法的前后打印日志,或者做一些邏輯判斷。此時我們無法去修改底層框架方法,那我們可以通過封裝一個代理類,在代理類中實現對方法的處理,然后所有的客戶端通過代理類去調用目標方法。
其中這里有幾個對象:
抽象角色:通過接口或者抽象類聲明真實角色實現的業務方法,盡可能的保證代理對象的內部結構和目標對象一致,這樣我們對代理對象的操作最終都可以轉移到目標對象上。代理角色/代理對象:實現抽象角色,是真實角色的代理,實現對目標方法的增強。真實角色/目標對象:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用。調用角色/客戶端:調用代理對象。
2. 靜態代理
2.1 業務場景
假設現在有這么個場景:王淑芬打算相親,但是自己嘴笨,於是找到媒婆,希望媒婆幫自己找個帥哥,於是找到了張鐵牛。
角色分析:
- 王淑芬:目標對象(被代理的人)。
- 媒婆:代理對象(代理了王淑芬,實現對目標方法的增強)。
- 張鐵牛:客戶端(訪問代理對象,即找媒婆)。
- 抽象角色(相親):
媒婆和王淑芬的共同目標-相親成功。

2.2 代碼實現
-
相親接口
/** * 相親抽象類 * * @author ludangxin * @date 2021/9/25 */ public interface BlindDateService { /** * 聊天 */ void chat(); } -
目標對象
/** * 王淑芬 - 目標對象 * * @author ludangxin * @date 2021/9/25 */ @Slf4j public class WangShuFen implements BlindDateService { @Override public void chat() { log.info("美女:王淑芬~"); } } -
代理對象
/** * 媒婆 - 代理對象 * * @author ludangxin * @date 2021/9/25 */ @Slf4j public class WomanMatchmaker implements BlindDateService { private BlindDateService bs; public WomanMatchmaker() { this.bs = new WangShuFen(); } @Override public void chat() { this.introduce(); bs.chat(); this.praise(); } /** * 介紹 */ private void introduce() { log.info("媒婆:她的工作是web前端~"); } /** * 誇人 */ private void praise() { log.info("媒婆:她就是有點害羞~"); log.info("媒婆:你看她人長的漂亮,而且溫柔賢惠,上的廳堂下的廚房~"); log.info("媒婆:而且寫bug超厲害~"); } } -
客戶端
/** * 張鐵牛 - client * * @author ludangxin * @date 2021/9/25 */ public class ZhangTieNiu { public static void main(String[] args) { WomanMatchmaker wm = new WomanMatchmaker(); wm.chat(); } } -
執行方法輸出內容如下:
22:44:51.184 [main] INFO proxy.staticp.WomanMatchmaker - 媒婆:她的工作是web前端~ 22:44:51.191 [main] INFO proxy.staticp.WangShuFen - 美女:你好,我叫王淑芬~ 22:44:51.191 [main] INFO proxy.staticp.WomanMatchmaker - 媒婆:她就是有點害羞~ 22:44:51.191 [main] INFO proxy.staticp.WomanMatchmaker - 媒婆:你看她人長的漂亮,而且溫柔賢惠,上的廳堂下的廚房~ 22:44:51.191 [main] INFO proxy.staticp.WomanMatchmaker - 媒婆:而且寫bug超厲害~
2.3 小節
好處:
- 耦合性降低。因為加入了代理類,調用者只用關心代理類即可,降低了調用者與目標類的耦合度。
- 指責清晰,目標對象只關心真實的業務邏輯。代理對象只負責對目標對象的增強。調用者只關心代理對象的執行結果。
- 代理對象實現了對目標方法的增強。也就是說:代理對象 = 增強代碼 + 目標對象。
缺陷:
每一個目標類都需要寫對應的代理類。如果當前系統已經有成百上千個類,工作量大太。所以,能不能不用寫那么多代理類,就能實現對目標方法的增強呢?
3. 動態代理
我們常見的動態代理一般有兩種:JDK動態代理和CGLib動態代理,本章只講JDK動態代理。
在了解JDK動態代理之前,先了解兩個重要的類。
3.1 Proxy

從JDK的幫助文檔中可知:
Proxy提供了創建動態代理和實例的靜態方法。即:
Proxy::newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
參數:
-
loader- 定義代理類的類加載器 -
interfaces- 代理類要實現的接口列表 -
h- 指派方法調用的調用處理程序
前兩個參數沒啥好說的,主要還需要了解下InvocationHandler。
3.2 InvocationHandler

從JDK的幫助文檔中可知:
InvocationHandler是一個接口,並且是調用處理邏輯實現的接口。在調用代理對象方法時,實際上是調用實現接口的invoke方法。
Object InvocationHandler::invoke(Object proxy, Method method, Object[] args)
參數:
-
proxy- 調用方法的代理實例。 -
method- 對應於在代理實例上調用的接口方法的Method實例。Method對象的聲明類將是在其中聲明方法的接口,該接口可以是代理類賴以繼承方法的代理接口的超接口。 -
args- 包含傳入代理實例上方法調用的參數值的對象數組,如果接口方法不使用參數,則為null。基本類型的參數被包裝在適當基本包裝器類(如java.lang.Integer或java.lang.Boolean)的實例中。
返回值:
從代理實例的方法調用返回的值。如果接口方法的聲明返回類型是基本類型,則此方法返回的值一定是相應基本包裝對象類的實例;否則,它一定是可分配到聲明返回類型的類型。如果此方法返回的值為 null 並且接口方法的返回類型是基本類型,則代理實例上的方法調用將拋出 NullPointerException。否則,如果此方法返回的值與上述接口方法的聲明返回類型不兼容,則代理實例上的方法調用將拋出 ClassCastException。
3.3 代碼實現
-
創建動態代理類
import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * 媒婆 - 代理對象 * * @author ludangxin * @date 2021/9/25 */ @Slf4j public class ProxyTest { public static Object getProxy(final Object target) { return Proxy.newProxyInstance( //類加載器 target.getClass().getClassLoader(), //讓代理對象和目標對象實現相同接口 target.getClass().getInterfaces(), //代理對象的方法最終都會被JVM導向它的invoke方法 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("proxy ===》{}", proxy.getClass().toGenericString()); log.info("method ===》{}", method.toGenericString()); log.info("args ===》{}", Arrays.toString(args)); introduce(); // 調用目標方法 Object result = method.invoke(target, args); praise(); return result; } }); } /** * 介紹 */ private static void introduce() { log.info("媒婆:她的工作是web前端~"); } /** * 誇人 */ private static void praise() { log.info("媒婆:她就是有點害羞~"); log.info("媒婆:你看她人長的漂亮,而且溫柔賢惠,上的廳堂下的廚房~"); log.info("媒婆:而且寫bug超厲害~"); } } -
客戶端
/** * 張鐵牛 - client * * @author ludangxin * @date 2021/9/25 */ public class ZhangTieNiu { public static void main(String[] args) { BlindDateService proxy = (BlindDateService)ProxyTest.getProxy(new WangShuFen()); proxy.chat(); } } -
執行方法輸出內容如下:
21:29:22.222 [main] INFO proxy.dynamic.ProxyTest - proxy ===》public final class com.sun.proxy.$Proxy0 21:29:22.229 [main] INFO proxy.dynamic.ProxyTest - method ===》public abstract void proxy.dynamic.BlindDateService.chat() 21:29:22.229 [main] INFO proxy.dynamic.ProxyTest - args ===》null 21:29:22.229 [main] INFO proxy.dynamic.ProxyTest - 媒婆:她的工作是web前端~ 21:29:22.229 [main] INFO proxy.dynamic.WangShuFen - 美女:你好,我叫王淑芬~ 21:29:22.230 [main] INFO proxy.dynamic.ProxyTest - 媒婆:她就是有點害羞~ 21:29:22.230 [main] INFO proxy.dynamic.ProxyTest - 媒婆:你看她人長的漂亮,而且溫柔賢惠,上的廳堂下的廚房~ 21:29:22.230 [main] INFO proxy.dynamic.ProxyTest - 媒婆:而且寫bug超厲害~
3.4 小節
其實代碼很簡單,只需要通過jdk提供的代理類創建一個代理類,再通過代理類去調用目標方法,並實現對方法的增強即可。
從打印的日志中可以看出JDK生成真實的代理對象為com.sun.proxy.$Proxy0,那我們能不能查看下生成的代理對象的源碼呢?答案肯定是可以的。我們可以借助JDK默認提供的ProxyGenerator::generateProxyClass()來輸出動態生成的代理對象。
改造下動態代理類如下:
添加輸出動態代理類的方法。
package proxy.dynamic;
import lombok.extern.slf4j.Slf4j;
import sun.misc.ProxyGenerator;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 媒婆 - 代理對象
*
* @author ludangxin
* @date 2021/9/25
*/
@Slf4j
public class ProxyTest {
public static Object getProxy(final Object target) {
return Proxy.newProxyInstance(
//類加載器
target.getClass().getClassLoader(),
//讓代理對象和目標對象實現相同接口
target.getClass().getInterfaces(),
//代理對象的方法最終都會被JVM導向它的invoke方法
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
generateProxyClass(proxy);
log.info("proxy ===》{}", proxy.getClass().toGenericString());
log.info("method ===》{}", method.toGenericString());
log.info("args ===》{}", Arrays.toString(args));
introduce();
Object result = method.invoke(target, args);
praise();
return result;
}
});
}
/**
* 輸出動態生成的代理類
* @param proxy 代理實例
*/
private static void generateProxyClass(Object proxy) {
byte[] bytes = ProxyGenerator.generateProxyClass(proxy.getClass().getName(), proxy.getClass().getInterfaces());
File file = new File("/Users/ludangxin/workspace/idea/test/target/classes/proxy/dynamic/proxy.class");
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(bytes);
} catch(Exception e) {
e.printStackTrace();
}
}
...
}
再次執行調用方法,輸出類如下:

類信息如下:

看完代理類的方法是不是恍然大悟,我們再整理下調用過程:

動態代理:動態的生成代理對象。
4. 總結
其實無論是靜態代理還是動態代理本質都是最終生成代理對象,區別在於靜態代理對象需要人手動生成,而動態代理對象是運行時,JDK通過反射動態生成的代理類最終構造的對象,JDK生成的類在加載到內存之后就刪除了,所以看不到類文件。
