目錄
一 靜態代理
1.1 優點
1.2 缺點
1.3 適用場景
?二 JDK動態代理
1 JDK動態代理的工作原理
1.1 創建代理類
1.2 加載代理類
1.3 實現方法調用
2. Proxy.newProxyInstance() 的核心工作流程
方法簽名
工作步驟
3. 代理類的生成與加載
3.1 代理類生成的關鍵方法
Proxy.getProxyClass()
ProxyGenerator.generateProxyClass()
3.2 代理類的加載
代理類是如何加載的?
代理類加載的時機
三?CGLIB動態代理
1. 代理方式
2. 核心組件
二、字節碼生成
1. 動態生成原理
2. 性能優化
3. 示例:生成的字節碼文件(偽代碼)
三、類加載與代理對象創建
1. 類加載流程
2. 突破限制
四、與 JDK 動態代理的關鍵區別
五、典型限制與解決方案
1. 無法代理 final 方法/類
2. 構造函數調用問題
六、應用場景
一 靜態代理
靜態代理就是手動編寫代理類,在編譯期就確定代理關系,并讓代理類和目標類實現相同的接口。代理類通過調用目標類的方法來完成任務,同時可以在調用前后添加一些額外的操作。
1.1 優點
- 簡單直觀:代碼結構清晰,易于理解和實現。
- 無侵入性:無需修改目標類代碼,通過代理類實現功能增強。
- 性能好:代理邏輯在編譯期確定,沒有動態生成的額外開銷。
1.2 缺點
- 冗余代碼:每個目標類都需要手動編寫對應的代理類,代碼量大。
- 靈活性差:代理關系在編譯期固定,無法動態切換代理邏輯。
- 接口依賴:若目標類沒有實現接口,則無法使用靜態代理(需通過繼承實現,類似CGLIB的思路,但需要手動編寫)。
1.3 適用場景
- 需要代理的類數量較少。
- 代理邏輯簡單且無需頻繁變更。
- 對性能要求高,避免動態代理的開銷。
public interface BaseSimpleService {public void save(Object obj);}
@Slf4j
public class BaseSimpleServiceImpl implements BaseSimpleService {@Overridepublic void save(Object obj) {System.out.println("對象保存成功");}}
/*** 對 BaseSimpleService類做一個增強,在不侵入原業務代碼的基礎上,實現日志記錄*/
@Slf4j
public class SimpleServiceProxy {private BaseSimpleService baseSimpleService;// 注入原對象public SimpleServiceProxy(BaseSimpleService baseSimpleService){this.baseSimpleService=baseSimpleService;}public void save(Object obj) {log.info("save obj:{}",obj);baseSimpleService.save(obj);log.info("保存: {}對象成功",obj);}
}
public class TestProxy {public static void main(String[] args) {// 基礎實現BaseSimpleService baseService=new BaseSimpleServiceImpl();Object obj = new Object();baseService.save(obj);// 靜態代理實現SimpleServiceProxy simpleServiceProxy=new SimpleServiceProxy(baseService);simpleServiceProxy.save(obj);// 原對象不受代理影響baseService.save(obj);}
}
?二 JDK動態代理
JDK動態代理的實現基于反射和類加載器, 通過 Proxy.newProxyInstance()
方法在運行時動態生成代理類,并將其加載到JVM中。
1 JDK動態代理的工作原理
Proxy.newProxyInstance()是JDK動態代理的核心方法,它會動態生成一個代理類,并返回該代理類的實例(即代理對象)。代理類的生成和加載涉及以下核心步驟
1.1 創建代理類
- 當調用
Proxy.newProxyInstance()
時,JVM 會動態生成一個代理類,該代理類繼承自java.lang.reflect.Proxy
并實現目標對象的接口。 - 代理類的名稱是動態生成的,形如
com.sun.proxy.$Proxy0
。$Proxy0
是 JDK 動態代理生成的代理類的默認命名規則。
1.2 加載代理類
- 生成的代理類會通過指定的類加載器(
ClassLoader
)加載到 JVM 中。 - 代理類的字節碼在內存中生成,并不會保存為
.class
文件。
1.3 實現方法調用
- 代理類會實現目標接口的所有方法,但這些方法的邏輯會被重定向到
InvocationHandler
的invoke()
方法。 - 代理類中,方法的實際調用行為是通過反射來完成的。
2. Proxy.newProxyInstance() 的核心工作流程
方法簽名
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
工作步驟
1 校驗參數:
-
- 檢查
ClassLoader
是否為null
。 - 檢查
interfaces
是否為空,并確保所有接口都是有效的(如不能是final
接口)。 - 檢查
InvocationHandler
是否為null
。
- 檢查
2?生成代理類:
-
- 調用
Proxy
類的私有方法getProxyClass0()
,動態生成代理類的字節碼。 - 代理類的字節碼是通過
sun.misc.ProxyGenerator
工具生成的。
- 調用
3?加載代理類:
-
- 使用指定的
ClassLoader
將代理類的字節碼加載到 JVM 中。
- 使用指定的
4?實例化代理類對象:
-
- 調用代理類的構造方法,傳入
InvocationHandler
對象。 - 返回動態生成的代理類的實例(即代理對象),它繼承自
java.lang.reflect.Proxy
并實現了Service
接口。
- 調用代理類的構造方法,傳入
3. 代理類的生成與加載
3.1 代理類生成的關鍵方法
Proxy.getProxyClass()
- 該方法通過目標接口數組生成代理類。
- 代理類的字節碼由
sun.misc.ProxyGenerator
工具生成。 - 代理類繼承自
java.lang.reflect.Proxy
,并實現了目標接口。
Class<?> proxyClass = Proxy.getProxyClass(loader, interfaces);
ProxyGenerator.generateProxyClass()
- 生成代理類的字節碼。
- 代理類的每個方法都會調用
InvocationHandler.invoke()
。 - 字節碼存儲在內存中,并不會生成
.class
文件。
3.2 代理類的加載
代理類是如何加載的?
- 代理類的字節碼通過
ClassLoader
加載到 JVM 中。 - 加載時會為代理類分配內存,并將其方法表注冊到 JVM 的方法區中。
代理類加載的時機
- 代理類是在
Proxy.newProxyInstance()
調用時動態生成并加載的。 - 每次調用
newProxyInstance()
都會檢查是否已存在對應接口的代理類。如果已存在,則直接加載;否則,重新生成代理類。
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;// 目標接口
interface BaseSimpleService {public void save(Object obj);}// 接口實現類
class BaseSimpleServiceImpl implements BaseSimpleService {@Overridepublic void save(Object obj) {System.out.println("對象保存成功");}}// 調用處理器 不改變目標對象方法的基礎上 記錄日志
@Slf4j
class JdkLoggingInvocationHandler implements InvocationHandler {private Object target;public JdkLoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.info("save obj:{}",args);Object result = method.invoke(target, args);log.info("保存: {}對象成功",args);return result;}}
import java.lang.reflect.Proxy;public class TestProxy {public static void main(String[] args) {BaseSimpleService baseSimpleService=new BaseSimpleServiceImpl();JdkLoggingInvocationHandler handler=new JdkLoggingInvocationHandler(baseSimpleService);// 該實例具有代理類的指定調用處理程序,該代理類由指定的類加載器定義并實現指定的接口// simpleServiceProxy代理類由指定的類加載器加載 并實現了BaseSimpleService的接口BaseSimpleService simpleServiceProxy = (BaseSimpleService) Proxy.newProxyInstance(baseSimpleService.getClass().getClassLoader(), baseSimpleService.getClass().getInterfaces(),handler);simpleServiceProxy.save(new Object());}
}
三?CGLIB動態代理
CGLIB(Code Generation Library)是一個基于字節碼生成的第三方庫,用于在運行時動態生成Java類的子類,從而實現對目標類的代理。它主要用于代理沒有實現接口的類,解決了JDK動態代理只能基于接口的局限性。其核心原理如下:
1. 代理方式
- 繼承式代理:生成目標類的子類(如
UserService$$EnhancerByCGLIB$$123456
),通過重寫父類非 final 方法實現代理。 - 無需接口:直接代理普通類,彌補 JDK 動態代理的局限性。
- 代理類會重寫目標方法,并在方法中插入攔截邏輯
2. 核心組件
- Enhancer:
負責生成代理類,配置代理策略(如回調方法、類加載器)。
// 創建 CGLIB 代理的 Enhancer對象Enhancer enhancer=new Enhancer();// 設置代理的目標類// CGLIB通過繼承這個目標類來生成代理類enhancer.setSuperclass(BaseService.class);// 設置方法攔截器,代理會調用這個攔截器enhancer.setCallback(new CglibLoggingMethodInterceptor(baseService));// 創建代理對象 當前的代理對象不是原始對象了 而是通過繼承目標類BaseService得到的新類BaseService baseServiceProxy = (BaseService) enhancer.create();baseServiceProxy.save(new Object());
- MethodInterceptor:
代理類的方法調用會被轉發到MethodInterceptor
接口的intercept
方法中,實現增強邏輯。
/**
* obj: 代理對象本身
* method: 被攔截的方法(目標方法的反射對象)
* args: 方法參數
* proxy: 方法代理對象,用于調用父類(目標類)的原始方法
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 前置增強(如日志)Object result = proxy.invokeSuper(obj, args); // 調用父類(目標類)原始方法// 后置增強(如事務提交)return result;
}
二、字節碼生成
1. 動態生成原理
- 基于 ASM:直接操作字節碼生成
.class
文件的二進制內容,避免源碼編譯。 - 方法重寫:
生成子類時,為每個非 final 方法生成重寫版本,插入攔截邏輯(調用MethodInterceptor
)。
2. 性能優化
- FastClass 機制:
為代理類和目標類的方法建立索引,直接通過索引調用方法,繞過了反射的Method.invoke()
,提升了性能。
// FastClass 通過索引調用方法(偽代碼)
public Object invoke(int methodIndex, Object obj, Object[] args) {switch (methodIndex) {case 0: return ((TargetClass)obj).method1();case 1: return ((TargetClass)obj).method2();}
}
3. 示例:生成的字節碼文件(偽代碼)
生成的代理類可能如下(簡化后的偽代碼):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Cat$$EnhancerByCGLIB$$8ca2de8b extends Cat implements Factory {private boolean CGLIB$BOUND;public static Object CGLIB$FACTORY_DATA;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;private MethodInterceptor CGLIB$CALLBACK_0;private static Object CGLIB$CALLBACK_FILTER;private static final Method CGLIB$sleep$0$Method;private static final MethodProxy CGLIB$sleep$0$Proxy;private static final Object[] CGLIB$emptyArgs;private static final Method CGLIB$wakeup$1$Method;private static final MethodProxy CGLIB$wakeup$1$Proxy;private static final Method CGLIB$equals$2$Method;private static final MethodProxy CGLIB$equals$2$Proxy;private static final Method CGLIB$toString$3$Method;private static final MethodProxy CGLIB$toString$3$Proxy;private static final Method CGLIB$hashCode$4$Method;private static final MethodProxy CGLIB$hashCode$4$Proxy;private static final Method CGLIB$clone$5$Method;private static final MethodProxy CGLIB$clone$5$Proxy;static void CGLIB$STATICHOOK4() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("com.example.demo.cglibproxy.vo.Cat$$EnhancerByCGLIB$$8ca2de8b");Class var1;Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());CGLIB$equals$2$Method = var10000[0];CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");CGLIB$toString$3$Method = var10000[1];CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");CGLIB$hashCode$4$Method = var10000[2];CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");CGLIB$clone$5$Method = var10000[3];CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");var10000 = ReflectUtils.findMethods(new String[]{"sleep", "()V", "wakeup", "()V"}, (var1 = Class.forName("com.example.demo.cglibproxy.vo.Cat")).getDeclaredMethods());CGLIB$sleep$0$Method = var10000[0];CGLIB$sleep$0$Proxy = MethodProxy.create(var1, var0, "()V", "sleep", "CGLIB$sleep$0");CGLIB$wakeup$1$Method = var10000[1];CGLIB$wakeup$1$Proxy = MethodProxy.create(var1, var0, "()V", "wakeup", "CGLIB$wakeup$1");}final void CGLIB$sleep$0() {super.sleep();}public final void sleep() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (this.CGLIB$CALLBACK_0 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$sleep$0$Method, CGLIB$emptyArgs, CGLIB$sleep$0$Proxy);} else {super.sleep();}}final void CGLIB$wakeup$1() {super.wakeup();}public final void wakeup() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (this.CGLIB$CALLBACK_0 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$wakeup$1$Method, CGLIB$emptyArgs, CGLIB$wakeup$1$Proxy);} else {super.wakeup();}}public static MethodProxy CGLIB$findMethodProxy(Signature var0) {String var10000 = var0.toString();switch(var10000.hashCode()) {case -1385928386:if (var10000.equals("sleep()V")) {return CGLIB$sleep$0$Proxy;}break;case -508378822:if (var10000.equals("clone()Ljava/lang/Object;")) {return CGLIB$clone$5$Proxy;}break;case 391780310:if (var10000.equals("wakeup()V")) {return CGLIB$wakeup$1$Proxy;}break;case 1826985398:if (var10000.equals("equals(Ljava/lang/Object;)Z")) {return CGLIB$equals$2$Proxy;}break;case 1913648695:if (var10000.equals("toString()Ljava/lang/String;")) {return CGLIB$toString$3$Proxy;}break;case 1984935277:if (var10000.equals("hashCode()I")) {return CGLIB$hashCode$4$Proxy;}}return null;}public Cat$$EnhancerByCGLIB$$8ca2de8b() {CGLIB$BIND_CALLBACKS(this);}public Cat$$EnhancerByCGLIB$$8ca2de8b(String var1) {super(var1);CGLIB$BIND_CALLBACKS(this);}public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {CGLIB$THREAD_CALLBACKS.set(var0);}public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {CGLIB$STATIC_CALLBACKS = var0;}private static final void CGLIB$BIND_CALLBACKS(Object var0) {Cat$$EnhancerByCGLIB$$8ca2de8b var1 = (Cat$$EnhancerByCGLIB$$8ca2de8b)var0;if (!var1.CGLIB$BOUND) {var1.CGLIB$BOUND = true;Object var10000 = CGLIB$THREAD_CALLBACKS.get();if (var10000 == null) {var10000 = CGLIB$STATIC_CALLBACKS;if (CGLIB$STATIC_CALLBACKS == null) {return;}}var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];}}public Object newInstance(Callback[] var1) {CGLIB$SET_THREAD_CALLBACKS(var1);Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Callback var1) {CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {CGLIB$SET_THREAD_CALLBACKS(var3);Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b;switch(var1.length) {case 0:var10000.<init>();break;case 1:if (var1[0].getName().equals("java.lang.String")) {var10000.<init>((String)var2[0]);break;}throw new IllegalArgumentException("Constructor not found");default:throw new IllegalArgumentException("Constructor not found");}CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Callback getCallback(int var1) {CGLIB$BIND_CALLBACKS(this);MethodInterceptor var10000;switch(var1) {case 0:var10000 = this.CGLIB$CALLBACK_0;break;default:var10000 = null;}return var10000;}public void setCallback(int var1, Callback var2) {switch(var1) {case 0:this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;default:}}public Callback[] getCallbacks() {CGLIB$BIND_CALLBACKS(this);return new Callback[]{this.CGLIB$CALLBACK_0};}public void setCallbacks(Callback[] var1) {this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];}static {CGLIB$STATICHOOK4();}
}
三、類加載與代理對象創建
1. 類加載流程
- 動態加載:生成的字節碼通過類加載器(默認目標類的 ClassLoader)加載到 JVM。
- 緩存優化:已生成的代理類會被緩存,避免重復生成。
2. 突破限制
- 繞過構造函數:
使用Objenesis
庫直接實例化代理對象,無需調用父類構造函數(即使父類有無參構造)。
Objenesis objenesis = new ObjenesisStd();
TargetClass proxy = objenesis.newInstance(proxyClass); // 直接實例化
四、與 JDK 動態代理的關鍵區別
特性 | CGLIB | JDK 動態代理 |
代理方式 | 繼承目標類生成子類 | 實現目標接口 |
目標要求 | 不能是 final 類/方法 | 必須實現接口 |
方法調用速度 | 快(FastClass 直接調用) | 慢(反射調用) |
內存消耗 | 較高(需生成子類) | 較低 |
五、典型限制與解決方案
1. 無法代理 final 方法/類
- 表現:若目標類或方法是 final,CGLIB 無法生成子類。
- 解決:重構代碼移除 final 修飾,或改用 JDK 動態代理。
2. 構造函數調用問題
- 表現:代理類會調用父類構造函數,若父類沒有無參構造函數會報錯。
- 解決:使用
Objenesis
繞過構造函數(需添加依賴)。
六、應用場景
- Spring AOP:默認對未實現接口的類使用 CGLIB 代理。
- 性能敏感場景:如高頻調用的工具類增強。
- 歷史代碼擴展:無法修改原有類/接口時,直接代理實現功能增強。
import org.springframework.cglib.proxy.Enhancer;public class TestProxy {public static void main(String[] args) {BaseService baseService=new BaseService();// 創建 CGLIB 代理的 Enhancer對象Enhancer enhancer=new Enhancer();// 設置代理的目標類// CGLIB通過繼承這個目標類來生成代理類enhancer.setSuperclass(BaseService.class);// 設置方法攔截器,代理會調用這個攔截器enhancer.setCallback(new CglibLoggingMethodInterceptor(baseService));// 創建代理對象 當前的代理對象不是原始對象了 而是通過繼承目標類BaseService得到的新類BaseService baseServiceProxy = (BaseService) enhancer.create();baseServiceProxy.save(new Object());}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;/*** 目標類*/
class BaseService {public void save(Object obj) {System.out.println("對象保存成功");}}@Slf4j
public class CglibLoggingMethodInterceptor implements MethodInterceptor {private Object target;public CglibLoggingMethodInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {log.info("save obj:{}",args);// 調用父類方法Object result = proxy.invokeSuper(obj, args);log.info("保存: {}對象成功",args);return result;}
}