首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 設計模式精講:代理模式 - 靜態代理、動態代理 (JDK/CGLIB) 與 Spring AOP 核心原理

     編輯:Java 綜合教學

模式定義與四大角色

代理模式:為目標對象提供一個代理對象,控制對目標對象的訪問,在不修改目標代碼前提下增強功能。

角色說明
Subject (主題接口)定義代理和目標對象的公共方法契約
RealSubject (真實主題)實際業務邏輯實現者
Proxy (代理類)持有 RealSubject 引用,實現 Subject,控制訪問、添加橫切邏輯
Client (客戶端)通過 Subject 接口調用 Proxy,無感知 RealSubject

核心應用場景

  • 延遲加載 (Virtual Proxy): Hibernate 懶加載、大圖片加載佔位符。
  • 遠程代理 (Remote Proxy): RMI、Dubbo、gRPC 存根。
  • 保護代理 (Protection Proxy): 權限校驗、敏感操作攔截。
  • 智能引用 (Smart Reference): 引用計數、線程安全包裝、緩存代理。
  • AOP 切面 (最常見): 事務、日誌、監控、重試。

靜態代理:手寫代理類

// 1. 主題接口
public interface IDBQuery { String request(); }

// 2. 真實主題 (昂貴資源初始化)
public class DBQuery implements IDBQuery {
    public DBQuery() { Thread.sleep(1000); /* 模擬建立連接 */ }
    @Override public String request() { return "真實數據"; }
}

// 3. 代理類 (延遲加載)
public class DBQueryProxy implements IDBQuery {
    private DBQuery real;
    @Override public synchronized String request() {
        if (real == null) real = new DBQuery(); // 首次調用才創建
        return real.request();
    }
}

// 4. 客戶端
public class Main { public static void main(String[] args) {
    IDBQuery proxy = new DBQueryProxy();
    System.out.println(proxy.request()); // 首次耗時 ~1s
    System.out.println(proxy.request()); // 後續極快
}}

痛點: 每個接口需手寫一個代理類,接口變更雙方同步修改,維護成本高。

動態代理:運行時生成字節碼

JDK 動態代理 (基於接口)

public class JDKProxyHandler implements InvocationHandler {
    private final Object target;
    public JDKProxyHandler(Object target) { this.target = target; }
    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【JDK代理】前置增強: " + method.getName());
        Object result = method.invoke(target, args); // 反射調用目標
        System.out.println("【JDK代理】後置增強");
        return result;
    }
    public static Object newProxyInstance(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(), // 必須實現接口
            new JDKProxyHandler(target));
    }
}

CGLIB 動態代理 (基於繼承,無需接口)

public class CglibProxy implements MethodInterceptor {
    private final Object target;
    public CglibProxy(Object target) { this.target = target; }
    @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("【CGLIB】事務開啟");
        Object result = proxy.invokeSuper(obj, args); // 調用父類方法
        System.out.println("【CGLIB】事務提交");
        return result;
    }
    public static Object newInstance(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new CglibProxy(target));
        return enhancer.create();
    }
}

JDK vs CGLIB 核心差異

維度JDK ProxyCGLIB
前提條件目標類必須實現一個或多個接口無需接口,通過繼承子類代理
核心機制生成實現接口的匿名類生成目標類的子類,重寫方法
final 方法可代理無法代理 (無法重寫)
性能 (調用)反射調用,稍慢FastClass 機制,直接索引調用,較快
Spring 默認策略有接口用 JDK,無接口用 CGLIB可強制 proxyTargetClass=true 全用 CGLIB

Spring AOP 底層原理

Spring AOP 本質是 動態代理:

  1. 掃描 @Aspect 切面類,解析 @Pointcut、@Before/@After/@Around。
  2. 創建 Advisor (切面 + 切點)。
  3. Bean 後置處理器 AnnotationAwareAspectJAutoProxyCreator 在 Bean 初始化後,判斷是否匹配切點。
  4. 匹配則創建代理對象 (JDK/CGLIB),將目標 Bean 包裝,註入代理到容器替換原 Bean。
  5. 運行時調用代理方法 → MethodInterceptor 鏈 → 織入通知邏輯 → 反射/調用父類執行目標方法。

代碼演示:Spring AOP 模擬事務管理

// 目標類 (無接口)
public class UserService {
    public void save(User u) { System.out.println("保存用戶: " + u.getName()); }
    public void delete(int id) { System.out.println("刪除用戶: " + id); }
}

// 切面
@Aspect
@Component
public class TxAspect {
    @Around("execution(* *.save(..)) || execution(* *.delete(..))")
    public Object manageTx(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("=== 開啟事務 ===");
        try {
            Object ret = pjp.proceed(); // 執行目標方法
            System.out.println("=== 提交事務 ===");
            return ret;
        } catch (Exception e) {
            System.out.println("=== 回滾事務 ===");
            throw e;
        }
    }
}

// 啟動類
@EnableAspectJAutoProxy(proxyTargetClass = true) // 強制 CGLIB
@SpringBootApplication
public class App { public static void main(String[] args) {
    ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
    UserService svc = ctx.getBean(UserService.class);
    svc.save(new User("張三")); // 控制台輸出:開啟事務 -> 保存用戶 -> 提交事務
}}

總結

代理模式是框架級開發的基石。掌握「靜態代理理解本質 → JDK 動態代理掌握接口代理 → CGLIB 掌握類代理 → Spring AOP 理解自動化織入」的遞進鏈路,能讓你在排查事務不生效、@Async 失效、循環依賴代理提前暴露等疑難雜症時,直擊字節碼層面本質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved