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

Java 靜態代理實戰:JDK 動態代理 vs CGLIB 代理原理與代碼對比

     編輯:Java 相關知識

代理模式為目標對象提供訪問控制層,是 AOP(面向切面編程)的核心實現。Java 靜態代理(實為動態代理)主要有 JDK 原生代理與 CGLIB 字節碼代理兩大流派,本文深度對比原理、代碼實現、優劣差異。

核心概念

  • 目標對象:被代理的業務類(如 UserServiceImpl)
  • 代理對象:攔截調用、織入增強邏輯的中介
  • 統一接口:目標與代理實現相同接口,調用方無感知

一、JDK 動態代理

原理

  • 基於接口:目標類必須實現一個或多個接口
  • 利用反射:Proxy.newProxyInstance(ClassLoader, Interface[], InvocationHandler) 生成實現接口的匿名類
  • InvocationHandler.invoke(proxy, method, args) 統一攔截所有方法調用

代碼實現

// 1. 業務接口
public interface UserService {
    String getName(int id);
    Integer getAge(int id);
}

// 2. 目標實現
public class UserServiceImpl implements UserService {
    public String getName(int id) { 
        System.out.println("------getName------"); return "cat"; 
    }
    public Integer getAge(int id) { 
        System.out.println("------getAge------"); return 10; 
    }
}

// 3. 處理器
public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getName".equals(method.getName())) {
            System.out.println("------before " + method.getName() + "------");
            Object result = method.invoke(target, args);
            System.out.println("------after " + method.getName() + "------");
            return result;
        }
        return method.invoke(target, args); // 其他方法直接透傳
    }
}

// 4. 測試
public class RunJDK {
    public static void main(String[] args) {
        UserService proxy = (UserService) new MyInvocationHandler().bind(new UserServiceImpl());
        System.out.println(proxy.getName(1)); // 輸出前置/後置增強
        System.out.println(proxy.getAge(1));  // 無增強
    }
}

輸出結果

------before getName------
------getName------
------after getName------
cat
------getAge------
10

局限性

只能代理實現接口的類。無接口類無法使用 JDK 代理。

二、CGLIB 代理

原理

  • 基於繼承:動態生成目標類的子類,重寫父類方法
  • 底層使用 ASM 字節碼操作框架,性能優於反射
  • 核心類:Enhancer(增強器)、MethodInterceptor(攔截器)、MethodProxy(快速調用)

代碼實現

public class CGLIBProxy implements MethodInterceptor {
    private Object target;
    
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 設置父類
        enhancer.setCallback(this);                // 設置攔截器
        return enhancer.create();                  // 生成代理實例
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("++++++before " + proxy.getSuperName() + "++++++");
        Object result = proxy.invokeSuper(obj, args); // 調用父類方法
        System.out.println("++++++after " + proxy.getSuperName() + "++++++");
        return result;
    }
}

// 測試
public class RunCGLIB {
    public static void main(String[] args) {
        UserService proxy = (UserService) new CGLIBProxy().getInstance(new UserServiceImpl());
        proxy.getName(1);
        proxy.getAge(1);
    }
}

輸出結果

++++++before CGLIB$getName$0++++++
getName
------getName------
++++++after CGLIB$getName$0++++++
++++++before CGLIB$getAge$1++++++
getAge
------getAge------
++++++after CGLIB$getAge$1++++++

局限性

  • 無法代理 final 類/方法(無法繼承/重寫)
  • 構造函數會被調用兩次(父類構造 + 子類構造)
  • 需引入第三方庫(Spring 內置 spring-core 中的 CGLIB 重打包版)

三、核心對比

維度JDK 動態代理CGLIB 代理
前提條件目標實現接口目標非 final 類
生成機制實現接口(組合)繼承目標類(繼承)
核心 APIProxy, InvocationHandlerEnhancer, MethodInterceptor
調用方式反射 Method.invokeFastClass 機制/MethodProxy
性能(JDK8+)優化後極快,差距微小傳統優勢,現在差距可忽略
代理類型接口代理類代理
Spring 默認策略有接口用 JDK,無接口用 CGLIBproxy-target-class=true 強制 CGLIB

四、框架應用場景

  • Spring AOP:方法級切面(事務、日誌、權限、緩存)
  • Struts2 攔截器:Action 執行前後處理
  • MyBatis Mapper:接口動態代理實現 SQL 執行
  • RPC 框架:Stub 代理遠程調用

總結

JDK 代理標準、輕量、無依賴,優先選擇;CGLIB 補足無接口場景,性能略優但引入依賴。現代 Spring Boot 2.x+ 默認 proxy-target-class=false(有接口用 JDK),理解兩者差異可在 AOP 不生效、final 方法無法增強、序列化報錯等場景快速定位根因。

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