目錄
代理模式
一 靜態代理
一、核心作用
二、使用場景
二 動態代理
一、核心作用
二、使用場景
具體實現:(初始)
具體實現:(改進)
一、核心業務邏輯
1. 接口?MathCalculator
2. 實現類?MathCalculatorImpl
二、日志工具模塊
日志工具類?logUtils
三、動態代理模塊
動態代理類?DynamicProxy
四、測試驗證模塊
測試方法?test03()
概念:AOP 面向切面編程(Aspect-Oriented Programming)
面向切面編程,能夠將那些與業務無關,卻為業務模塊所調用的邏輯封裝起來,以減少系統代碼的重復度,減少模塊之間的耦合度。
代理模式
對比維度 | 靜態代理 | 動態代理 |
---|---|---|
代理關系確定時機 | 編碼期間:代理類需手動編寫,明確代理目標 | 運行期間:代理類由程序動態生成,無需預定義 |
實現方式 | 代理類與目標類實現相同接口(或繼承目標類),并通過組合持有目標對象 | 通過反射(JDK動態代理)或字節碼生成(CGLIB)動態攔截目標對象方法 |
代理范圍 | - 僅能代理同一接口/父類的類 - 需為每個目標類單獨編寫代理類 | -?JDK動態代理:代理接口實現類 -?CGLIB:代理無接口類(非final類) |
代碼復雜度 | - 代碼簡單直觀,適合新手 - 需手動維護代理類 | - 需理解反射/字節碼機制 - 框架(如Spring)可簡化開發 |
性能 | - 直接調用目標方法,無反射開銷 - 性能更高 | -?JDK動態代理:反射調用性能較低 -?CGLIB:首次生成代理類較慢 |
靈活性 | - 擴展功能固定,僅針對特定類 - 接口變動需同步修改代理類 | - 可批量代理多個類,統一添加功能(如日志、事務) - 接口變動自動適配 |
典型應用場景 | - 代理少量固定類 - 高頻調用場景(如核心服務) | - 框架級功能擴展(Spring AOP) - 需統一管理多個類的增強邏輯 |
靜態代理:編碼期間就決定好了代理關系
定義:代理對象是目標對象的子類型,代理對象本省并不是目標對象,而是將目標作為自己的屬性
優點:同一種類型的所有對象都能代理
缺點:范圍太小
動態代理:運行期間才決定好了代理關系
定義:目標對象會在執行期間被動態攔截,插入指定邏輯
優點:可以代理世間萬物
缺點:復雜不好寫
一 靜態代理
靜態代理是通過手動編寫一個“替身類”(代理類),在不修改原始類代碼的前提下,為其添加額外功能(如日志、權限校驗),使用時通過代理類間接調用原始類。
一、核心作用
-
控制訪問:限制或增強對原始對象的訪問(如權限校驗)。
-
功能擴展:在不修改原始對象代碼的前提下,添加新功能(如日志記錄、性能統計)。
二、使用場景
-
日志記錄:在方法調用前后自動記錄日志。
-
權限校驗:調用方法前檢查用戶權限。
-
延遲加載:在需要時才初始化復雜對象(如大文件加載)。
-
簡化復雜操作:隱藏底層復雜邏輯,對外提供簡潔接口。
-
避坑提示:如果系統中有大量需要代理的類,建議改用動態代理(如JDK動態代理或CGLIB)。
代碼實現:
定義接口:
package org.example.spring02.MathMethod;public interface MathCalculator {//定義四則運算public int div(int a, int b);public int mul(int a, int b);public int sub(int a, int b);public int add(int a, int b);}
實現類:
?
package org.example.spring02.MathMethod.MIm;import org.example.spring02.MathMethod.MathCalculator;
import org.springframework.stereotype.Component;@Component
public class MathCalculatorImpl implements MathCalculator {@Overridepublic int add(int a, int b) {return a + b;}@Overridepublic int sub(int a, int b) {return a - b;}@Overridepublic int mul(int a, int b) {return a * b;}@Overridepublic int div(int a, int b) {return a / b;}}
靜態代理類:
package org.example.spring02.MathMethod.proxy.statics;import lombok.Data;
import org.example.spring02.MathMethod.MathCalculator;/*** 靜態代理類,實現了MathCalculator接口* 本類用于在不修改原始類的情況下,增加額外的功能處理,如日志、權限校驗等
*/@Data
public class CalculatorStaticProxy implements MathCalculator {// 被代理對象private MathCalculator target;public CalculatorStaticProxy(MathCalculator mc) {this.target = mc;}@Overridepublic int div(int a, int b) {//書寫日志System.out.println("div被調用了,參數是:" + a + "," + b);System.out.println("div被調用了,結果是:" + target.div(a, b));return target.div(a, b);}@Overridepublic int mul(int a, int b) {System.out.println("mul被調用了,參數是:" + a + "," + b);System.out.println("mul被調用了,結果是:" + target.mul(a, b));return target.mul(a, b);}@Overridepublic int sub(int a, int b) {System.out.println("sub被調用了,參數是:" + a + "," + b);System.out.println("sub被調用了,結果是:" + target.sub(a, b));return target.sub(a, b);}@Overridepublic int add(int a, int b) {System.out.println("add被調用了,參數是:" + a + "," + b);System.out.println("add被調用了,結果是:" + target.add(a, b));return target.add(a, b);}
}
測試類:
// JUnit測試方法,用于測試MathCalculator的功能@Testvoid test01() {// 創建MathCalculator的實例對象MathCalculator target = new MathCalculatorImpl();// 調用add方法進行加法運算,并打印結果int add1 = target.add(1, 3);System.out.println(add1);// 分隔符,用于區分不同的測試部分System.out.println("======");// 創建靜態代理對象,并傳入目標對象CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(target);// 通過靜態代理對象調用add方法進行加法運算,并打印結果int add = calculatorStaticProxy.add(1, 3);System.out.println(add);}
運行展示:
二 動態代理
動態代理:JDK動態代理目標對象必須有接口,代理的也只是接口規定的方法
動態代理:是在運行時通過反射或字節碼技術自動生成代理類,無需手動編寫“替身類”,即可為多個目標對象統一添加功能(如日志、事務管理)。
攔截處理:動態代理對象在調用原生對象的方法時,通過統一的入口(如?InvocationHandler.invoke()
)攔截方法調用,從而實現對參數、方法執行過程和返回結果的增強或修改。
一、核心作用
-
統一擴展功能:批量對多個類添加相同的增強邏輯。
-
解耦代碼:將核心業務邏輯與輔助功能(如權限校驗)分離。
-
動態適配:運行時根據需求靈活生成代理對象。
二、使用場景
-
框架級功能擴展:如Spring AOP(日志、事務、權限)。
-
RPC調用:隱藏網絡通信細節,讓遠程調用像本地調用。
-
接口監控:統計接口調用耗時、成功率等。
-
Mock測試:動態生成測試用的代理對象。
1. ClassLoader loader
-
作用:
動態代理生成的字節碼需要被類加載器加載到 JVM 中。使用目標對象的類加載器可確保代理類能訪問目標類及其接口。 -
示例:
// 目標對象
UserService target = new UserServiceImpl();
// 使用目標對象的類加載器
ClassLoader loader = target.getClass().getClassLoader();
2.Class<?>[] interfaces
-
作用:
指定代理類需要實現的接口。代理對象會實現這些接口的所有方法,并將方法調用委托給?InvocationHandler
。 -
示例:
// 目標對象實現的接口
Class<?>[] interfaces = target.getClass().getInterfaces();
3.InvocationHandler h
-
作用:
定義代理邏輯的核心處理器。所有通過代理對象調用的方法,都會觸發?invoke()
?方法,開發者可在此插入增強邏輯。 -
示例:
InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 增強邏輯(如記錄日志)System.out.println("調用方法: " + method.getName());// 調用目標對象的方法return method.invoke(target, args);}
};
具體實現:(初始)
@Testvoid test02() {// 原生對象MathCalculator target = new MathCalculatorImpl();int add1 = target.add(1, 3);System.out.println("原生對象");System.out.println(add1);//創建動態代理/*三個參數proxy:代理對象->相當于明星的代理人method:代理對象調用目標對象的方法args:方法調用傳遞的參數*/InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 調用目標對象的方法并返回System.out.println("動態代理InvocationHandler的invoke中定義的");System.out.println("[日志] 調用了方法: " + method.getName());System.out.println("[日志] 獲得參數: " + Arrays.toString(args));return method.invoke(target, args);}};/*三個參數ClassLoader:確保代理類的正確加載。Interfaces:定義代理類的行為范圍(基于接口)。InvocationHandler:實現動態代理的核心邏輯(AOP 的基礎)。*/MathCalculator proxy = (MathCalculator) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),invocationHandler);int add = proxy.add(1, 3);System.out.println(add);}
具體實現:(改進)
一、核心業務邏輯
1. 接口?MathCalculator
-
作用:定義業務契約,明確四則運算的規范。
-
關鍵點:
-
聲明了加減乘除四個方法。
-
作為動態代理的接口約束(JDK動態代理必須基于接口)。
-
2. 實現類?MathCalculatorImpl
-
作用:具體實現四則運算的業務邏輯。
-
關鍵點:
-
使用?
@Component
?標注為 Spring 組件(若配合 Spring 使用)。 -
實現了?
MathCalculator
?接口中的方法。 -
是動態代理的目標對象(原生對象)。
-
接口
package org.example.spring02.MathMethod;public interface MathCalculator {//定義四則運算public int div(int a, int b);public int mul(int a, int b);public int sub(int a, int b);public int add(int a, int b);}
實現類
package org.example.spring02.MathMethod.MIm;import org.example.spring02.MathMethod.MathCalculator;
import org.springframework.stereotype.Component;@Component
public class MathCalculatorImpl implements MathCalculator {@Overridepublic int add(int a, int b) {return a + b;}@Overridepublic int sub(int a, int b) {return a - b;}@Overridepublic int mul(int a, int b) {return a * b;}@Overridepublic int div(int a, int b) {return a / b;}}
二、日志工具模塊
日志工具類?logUtils
-
作用:提供靜態方法統一管理日志格式。
-
核心方法:
-
logStart()
:記錄方法開始執行的日志(含方法名和參數)。 -
logEnd()
:記錄方法正常結束的日志(含結果)。 -
logException()
:記錄方法執行異常的日志。
-
-
設計亮點:
-
使用?
Object... args
?支持可變參數,適配不同方法的參數。 -
通過?
Arrays.toString(args)
?格式化參數輸出。
-
package org.example.spring02.log;import java.util.Arrays;public class logUtils {public static void logStart(String name , Object... args){System.out.println("開始執行方法:"+name+",參數:"+ Arrays.toString(args));}public static void logEnd(String name , Object result){System.out.println("方法:"+name+",執行結果:"+result);}public static void logException(String name , Exception e){System.out.println("方法:"+name+",執行異常:"+e.getMessage());}public static void logEnd(){System.out.println("方法執行結束");}}
三、動態代理模塊
動態代理類?DynamicProxy
-
作用:生成代理對象,為原生對象的方法調用添加日志增強邏輯。
package org.example.spring02.MathMethod.proxy.dynamic;import org.example.spring02.log.logUtils;import java.lang.reflect.Proxy;
import java.util.Arrays;public class DynamicProxy {public static Object newProxyInstance(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy, method, args) -> {System.out.println("動態代理開始");System.out.println("[日志] 調用了方法: " + method.getName());logUtils.logStart(method.getName(), args);System.out.println("[日志] 獲得參數: " + Arrays.toString(args));return method.invoke(target, args);});}
}
四、測試驗證模塊
測試方法?test03()
-
作用:驗證動態代理的日志增強功能是否生效。
@Testvoid test03(){MathCalculator o = (MathCalculator) DynamicProxy.newProxyInstance(new MathCalculatorImpl());int add = o.add(1, 3);System.out.println(add);}