SpringBoot項(xiàng)目中JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理的使用詳解
在 Spring Boot 項(xiàng)目中,JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理都是實(shí)現(xiàn) AOP (面向切面編程) 的重要技術(shù)。 它們的主要區(qū)別在于代理對(duì)象的生成方式和適用范圍。
下面詳細(xì)介紹它們的使用場(chǎng)景:
1. JDK 動(dòng)態(tài)代理 (JDK Dynamic Proxy)
原理:
- JDK 動(dòng)態(tài)代理是 Java 提供的內(nèi)置代理機(jī)制,它通過(guò)反射在運(yùn)行時(shí)動(dòng)態(tài)地生成代理類(lèi)。
- 代理類(lèi)會(huì)實(shí)現(xiàn)目標(biāo)類(lèi)實(shí)現(xiàn)的接口,并將接口中的方法調(diào)用委托給一個(gè)
InvocationHandler
對(duì)象來(lái)處理。 InvocationHandler
接口負(fù)責(zé)實(shí)現(xiàn)具體的增強(qiáng)邏輯,例如日志記錄、安全檢查、事務(wù)管理等。
使用場(chǎng)景:
- 目標(biāo)類(lèi)實(shí)現(xiàn)了接口: 如果目標(biāo)類(lèi)實(shí)現(xiàn)了接口,則 Spring AOP 默認(rèn)使用 JDK 動(dòng)態(tài)代理。
- 簡(jiǎn)單 AOP 場(chǎng)景: 適用于簡(jiǎn)單的 AOP 場(chǎng)景,例如只需要對(duì)接口方法進(jìn)行增強(qiáng)的情況。
- 不需要代理類(lèi)的構(gòu)造函數(shù): JDK 動(dòng)態(tài)代理創(chuàng)建代理對(duì)象時(shí),不需要調(diào)用目標(biāo)類(lèi)的構(gòu)造函數(shù)。
優(yōu)點(diǎn):
- 簡(jiǎn)單易用: JDK 動(dòng)態(tài)代理是 Java 內(nèi)置的代理機(jī)制,使用起來(lái)比較簡(jiǎn)單。
- 不需要第三方庫(kù): 不需要依賴第三方庫(kù)。
- 對(duì)接口友好: 代理類(lèi)實(shí)現(xiàn)了目標(biāo)類(lèi)實(shí)現(xiàn)的接口,符合面向接口編程的思想。
缺點(diǎn):
- 必須實(shí)現(xiàn)接口: 目標(biāo)類(lèi)必須實(shí)現(xiàn)接口,否則無(wú)法使用 JDK 動(dòng)態(tài)代理。
- 只能代理接口方法: 只能代理接口中定義的方法,無(wú)法代理類(lèi)自身定義的方法。
示例代碼:
// 接口 interface MyInterface { void doSomething(); } // 實(shí)現(xiàn)類(lèi) class MyClass implements MyInterface { @Override public void doSomething() { System.out.println("MyClass is doing something..."); } } // 調(diào)用處理器 class MyInvocationHandler implements InvocationHandler { private Object target; // 被代理的對(duì)象 public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method: " + method.getName()); // 前置增強(qiáng) Object result = method.invoke(target, args); // 調(diào)用原始方法 System.out.println("After method: " + method.getName()); // 后置增強(qiáng) return result; } } // 使用 JDK 動(dòng)態(tài)代理 public class JDKDynamicProxyExample { public static void main(String[] args) { MyInterface target = new MyClass(); // 創(chuàng)建目標(biāo)對(duì)象 MyInvocationHandler handler = new MyInvocationHandler(target); // 創(chuàng)建調(diào)用處理器 // 創(chuàng)建代理對(duì)象 MyInterface proxy = (MyInterface) Proxy.newProxyInstance( MyInterface.class.getClassLoader(), new Class[] {MyInterface.class}, handler ); proxy.doSomething(); // 調(diào)用代理對(duì)象的方法,會(huì)被攔截 } }
2. CGLIB 動(dòng)態(tài)代理 (CGLIB Dynamic Proxy)
原理:
- CGLIB (Code Generation Library) 是一個(gè)強(qiáng)大的、高性能的代碼生成庫(kù)。
- CGLIB 動(dòng)態(tài)代理通過(guò)在運(yùn)行時(shí)動(dòng)態(tài)地生成目標(biāo)類(lèi)的子類(lèi)來(lái)實(shí)現(xiàn)代理。
- 代理類(lèi)會(huì)繼承目標(biāo)類(lèi),并重寫(xiě)目標(biāo)類(lèi)的方法,在重寫(xiě)的方法中實(shí)現(xiàn)增強(qiáng)邏輯。
- CGLIB 動(dòng)態(tài)代理不需要目標(biāo)類(lèi)實(shí)現(xiàn)接口,可以直接代理類(lèi)。
使用場(chǎng)景:
- 目標(biāo)類(lèi)沒(méi)有實(shí)現(xiàn)接口: 如果目標(biāo)類(lèi)沒(méi)有實(shí)現(xiàn)接口,則 Spring AOP 使用 CGLIB 動(dòng)態(tài)代理。
- 需要代理類(lèi)自身定義的方法: 需要代理類(lèi)自身定義的方法,而不僅僅是接口方法。
- 需要更高的性能: 在某些情況下,CGLIB 動(dòng)態(tài)代理的性能可能比 JDK 動(dòng)態(tài)代理更好。
優(yōu)點(diǎn):
- 不需要實(shí)現(xiàn)接口: 目標(biāo)類(lèi)不需要實(shí)現(xiàn)接口,可以直接代理類(lèi)。
- 可以代理類(lèi)自身定義的方法: 可以代理類(lèi)自身定義的方法,而不僅僅是接口方法。
- 性能較好: 在某些情況下,CGLIB 動(dòng)態(tài)代理的性能可能比 JDK 動(dòng)態(tài)代理更好。
缺點(diǎn):
- 需要第三方庫(kù): 需要依賴 CGLIB 庫(kù)。
- 實(shí)現(xiàn)復(fù)雜: CGLIB 動(dòng)態(tài)代理的實(shí)現(xiàn)比較復(fù)雜,需要生成目標(biāo)類(lèi)的子類(lèi)。
- final 方法無(wú)法代理: 無(wú)法代理被
final
修飾的方法。 - 對(duì)構(gòu)造函數(shù)有要求: CGLIB 創(chuàng)建代理對(duì)象時(shí),需要調(diào)用目標(biāo)類(lèi)的構(gòu)造函數(shù)。 如果目標(biāo)類(lèi)沒(méi)有無(wú)參構(gòu)造函數(shù),則需要手動(dòng)指定構(gòu)造函數(shù)。
示例代碼:
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; // 目標(biāo)類(lèi) class MyClass { public void doSomething() { System.out.println("MyClass is doing something..."); } } // 方法攔截器 class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before method: " + method.getName()); // 前置增強(qiáng) Object result = proxy.invokeSuper(obj, args); // 調(diào)用原始方法 System.out.println("After method: " + method.getName()); // 后置增強(qiáng) return result; } } // 使用 CGLIB 動(dòng)態(tài)代理 public class CGLIBDynamicProxyExample { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); // 創(chuàng)建 Enhancer 對(duì)象 enhancer.setSuperclass(MyClass.class); // 設(shè)置超類(lèi) enhancer.setCallback(new MyMethodInterceptor()); // 設(shè)置回調(diào) MyClass proxy = (MyClass) enhancer.create(); // 創(chuàng)建代理對(duì)象 proxy.doSomething(); // 調(diào)用代理對(duì)象的方法,會(huì)被攔截 } }
3. Spring Boot 中的 AOP 配置
在 Spring Boot 項(xiàng)目中,可以通過(guò)以下方式配置 AOP:
添加 AOP 依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
編寫(xiě)切面類(lèi):
@Aspect @Component public class LoggingAspect { @Before("execution(* com.example.demo.service.*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature()); } }
配置代理方式:
- 默認(rèn)情況下,Spring AOP 會(huì)根據(jù)目標(biāo)類(lèi)是否實(shí)現(xiàn)了接口來(lái)選擇使用 JDK 動(dòng)態(tài)代理或 CGLIB 動(dòng)態(tài)代理。
- 可以通過(guò)
@EnableAspectJAutoProxy
注解的proxyTargetClass
屬性來(lái)強(qiáng)制使用 CGLIB 動(dòng)態(tài)代理。
@Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) // 強(qiáng)制使用 CGLIB 動(dòng)態(tài)代理 public class AopConfig { // ... }
總結(jié):
特性 | JDK 動(dòng)態(tài)代理 | CGLIB 動(dòng)態(tài)代理 |
---|---|---|
目標(biāo)類(lèi)要求 | 必須實(shí)現(xiàn)接口 | 不需要實(shí)現(xiàn)接口 |
代理對(duì)象生成方式 | 實(shí)現(xiàn)接口 | 繼承類(lèi) |
性能 | 一般 | 較好 |
易用性 | 簡(jiǎn)單 | 復(fù)雜 |
是否需要第三方庫(kù) | 否 | 是 (net.sf.cglib) |
適用場(chǎng)景 | 目標(biāo)類(lèi)實(shí)現(xiàn)了接口,簡(jiǎn)單 AOP 場(chǎng)景 | 目標(biāo)類(lèi)沒(méi)有實(shí)現(xiàn)接口,需要代理類(lèi)自身定義的方法,性能要求較高 |
@EnableAspectJAutoProxy | 默認(rèn)值:false | proxyTargetClass = true |
在實(shí)際開(kāi)發(fā)中,Spring AOP 會(huì)自動(dòng)選擇合適的代理方式。 如果沒(méi)有特殊需求,可以使用默認(rèn)配置。
如果需要強(qiáng)制使用 CGLIB 動(dòng)態(tài)代理,可以設(shè)置
@EnableAspectJAutoProxy(proxyTargetClass = true)
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot快速接入OpenAI大模型的方法(JDK8)
- SpringBoot接入deepseek深度求索示例代碼(jdk1.8)
- 關(guān)于JDK8升級(jí)17及springboot?2.x升級(jí)3.x詳細(xì)指南
- Java搭建一個(gè)springboot3.4.1項(xiàng)目?JDK21的詳細(xì)過(guò)程
- SpringBoot配置開(kāi)發(fā)環(huán)境的詳細(xì)步驟(JDK、Maven、IDEA等)
- IDEA無(wú)法創(chuàng)建JDK1.8版本的Springboot項(xiàng)目問(wèn)題解決(2種方法)
- 查看SpringBoot和JDK版本對(duì)應(yīng)關(guān)系的方法
相關(guān)文章
java編程調(diào)用存儲(chǔ)過(guò)程中得到新增記錄id號(hào)的實(shí)現(xiàn)方法
這篇文章主要介紹了java編程調(diào)用存儲(chǔ)過(guò)程中得到新增記錄id號(hào)的實(shí)現(xiàn)方法,涉及Java數(shù)據(jù)庫(kù)操作中存儲(chǔ)過(guò)程的相關(guān)使用技巧,需要的朋友可以參考下2015-10-10Jenkins Pipeline為Kubernetes應(yīng)用部署增加狀態(tài)檢測(cè)腳本優(yōu)化
這篇文章主要為大家介紹了Jenkins Pipeline為Kubernetes應(yīng)用部署增加狀態(tài)檢測(cè)腳本優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12SpringBoot獲取http數(shù)據(jù)、打印HTTP參數(shù)的4種方式
Java的話本地打斷點(diǎn)可以調(diào)試獲取rest入?yún)?但是在生產(chǎn)環(huán)境可能我們獲取入?yún)ⅲ℉ttp?header/parameter)可能就沒(méi)有那么的輕松了,所以本文給大家介紹了SpringBoot獲取http數(shù)據(jù)、打印HTTP參數(shù)的4種方式,需要的朋友可以參考下2024-03-03springboot如何通過(guò)不同的策略動(dòng)態(tài)調(diào)用不同的實(shí)現(xiàn)類(lèi)
這篇文章主要介紹了springboot如何通過(guò)不同的策略動(dòng)態(tài)調(diào)用不同的實(shí)現(xiàn)類(lèi),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02SpringCloud-Alibaba-Sentinel-配置持久化策略詳解
這篇文章主要介紹了SpringCloud-Alibaba-Sentinel-配置持久化策略,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03零基礎(chǔ)如何系統(tǒng)的學(xué)習(xí)Java
這篇文章主要介紹了零基礎(chǔ)如何系統(tǒng)的學(xué)習(xí)Java,很多朋友糾結(jié)這個(gè)問(wèn)題,教材書(shū)不知道從何學(xué)起,今天小編給大家分享一篇教程幫助到家梳理這方面的知識(shí)2020-07-07java實(shí)現(xiàn)肯德基收銀系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)肯德基收銀系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05