SpringBoot中5種動態(tài)代理的實現(xiàn)方案
動態(tài)代理允許我們在不修改源代碼的情況下,為對象增加額外的行為。在SpringBoot應(yīng)用中,動態(tài)代理被廣泛用于實現(xiàn)事務(wù)管理、緩存、安全控制、日志記錄等橫切關(guān)注點。
1. JDK動態(tài)代理:Java原生的代理方案
實現(xiàn)原理
JDK動態(tài)代理是Java標(biāo)準(zhǔn)庫提供的代理機(jī)制,基于java.lang.reflect.Proxy
類和InvocationHandler
接口實現(xiàn)。它通過反射在運(yùn)行時動態(tài)創(chuàng)建接口的代理實例。
核心代碼示例
public class JdkDynamicProxyDemo { interface UserService { void save(User user); User find(Long id); } // 實現(xiàn)類 static class UserServiceImpl implements UserService { @Override public void save(User user) { System.out.println("保存用戶: " + user.getName()); } @Override public User find(Long id) { System.out.println("查找用戶ID: " + id); return new User(id, "用戶" + id); } } // 調(diào)用處理器 static class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before: " + method.getName()); long startTime = System.currentTimeMillis(); Object result = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println("After: " + method.getName() + ", 耗時: " + (endTime - startTime) + "ms"); return result; } } public static void main(String[] args) { // 創(chuàng)建目標(biāo)對象 UserService userService = new UserServiceImpl(); // 創(chuàng)建代理對象 UserService proxy = (UserService) Proxy.newProxyInstance( userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new LoggingInvocationHandler(userService) ); // 調(diào)用代理方法 proxy.save(new User(1L, "張三")); User user = proxy.find(2L); } }
優(yōu)點
- JDK標(biāo)準(zhǔn)庫自帶:無需引入額外依賴,減少了項目體積
- 生成代碼簡單:代理邏輯集中在InvocationHandler中,易于理解和維護(hù)
- 性能相對穩(wěn)定:在JDK 8后的版本中,性能有明顯提升
局限性
- 只能代理接口:被代理類必須實現(xiàn)接口,無法代理類
- 反射調(diào)用開銷:每次方法調(diào)用都需通過反射機(jī)制,有一定性能損耗
- 無法攔截final方法:無法代理被final修飾的方法
Spring中的應(yīng)用
在Spring中,當(dāng)Bean實現(xiàn)了接口時,默認(rèn)使用JDK動態(tài)代理。這可以通過配置修改:
@EnableAspectJAutoProxy(proxyTargetClass = false) // 默認(rèn)值為false,表示優(yōu)先使用JDK動態(tài)代理
2. CGLIB代理:基于字節(jié)碼的強(qiáng)大代理
實現(xiàn)原理
CGLIB(Code Generation Library)是一個強(qiáng)大的高性能字節(jié)碼生成庫,它通過繼承被代理類生成子類的方式實現(xiàn)代理。Spring從3.2版本開始將CGLIB直接集成到框架中。
核心代碼示例
public class CglibProxyDemo { // 不需要實現(xiàn)接口的類 static class UserService { public void save(User user) { System.out.println("保存用戶: " + user.getName()); } public User find(Long id) { System.out.println("查找用戶ID: " + id); return new User(id, "用戶" + id); } } // CGLIB方法攔截器 static class LoggingMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before: " + method.getName()); long startTime = System.currentTimeMillis(); // 調(diào)用原始方法 Object result = proxy.invokeSuper(obj, args); long endTime = System.currentTimeMillis(); System.out.println("After: " + method.getName() + ", 耗時: " + (endTime - startTime) + "ms"); return result; } } public static void main(String[] args) { // 創(chuàng)建CGLIB代理 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new LoggingMethodInterceptor()); // 創(chuàng)建代理對象 UserService proxy = (UserService) enhancer.create(); // 調(diào)用代理方法 proxy.save(new User(1L, "張三")); User user = proxy.find(2L); } }
優(yōu)點
- 可以代理類:不要求目標(biāo)類實現(xiàn)接口,應(yīng)用場景更廣泛
- 性能較高:通過生成字節(jié)碼而非反射調(diào)用,方法調(diào)用性能優(yōu)于JDK代理
- 功能豐富:支持多種回調(diào)類型,如LazyLoader、Dispatcher等
- 集成到Spring:Spring框架已內(nèi)置CGLIB,無需額外依賴
局限性
- 無法代理final類和方法:由于使用繼承機(jī)制,無法代理final修飾的類或方法
- 構(gòu)造函數(shù)調(diào)用:在生成代理對象時會調(diào)用目標(biāo)類的構(gòu)造函數(shù),可能導(dǎo)致意外行為
- 復(fù)雜性增加:生成的字節(jié)碼復(fù)雜度高,調(diào)試?yán)щy
- 對Java版本敏感:在不同Java版本間可能存在兼容性問題
Spring中的應(yīng)用
在Spring中,當(dāng)Bean沒有實現(xiàn)接口或配置了proxyTargetClass=true
時,使用CGLIB代理:
@EnableAspectJAutoProxy(proxyTargetClass = true) // 強(qiáng)制使用CGLIB代理
3. ByteBuddy:現(xiàn)代化的字節(jié)碼操作庫
實現(xiàn)原理
ByteBuddy是一個相對較新的字節(jié)碼生成和操作庫,設(shè)計更加現(xiàn)代化,API更加友好。它可以創(chuàng)建和修改Java類,而無需理解底層的JVM指令集。
核心代碼示例
public class ByteBuddyProxyDemo { interface UserService { void save(User user); User find(Long id); } static class UserServiceImpl implements UserService { @Override public void save(User user) { System.out.println("保存用戶: " + user.getName()); } @Override public User find(Long id) { System.out.println("查找用戶ID: " + id); return new User(id, "用戶" + id); } } static class LoggingInterceptor { @RuntimeType public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object[] args) throws Exception { System.out.println("Before: " + method.getName()); long startTime = System.currentTimeMillis(); Object result = callable.call(); long endTime = System.currentTimeMillis(); System.out.println("After: " + method.getName() + ", 耗時: " + (endTime - startTime) + "ms"); return result; } } public static void main(String[] args) throws Exception { UserService userService = new UserServiceImpl(); // 創(chuàng)建ByteBuddy代理 UserService proxy = new ByteBuddy() .subclass(UserServiceImpl.class) .method(ElementMatchers.any()) .intercept(MethodDelegation.to(new LoggingInterceptor())) .make() .load(UserServiceImpl.class.getClassLoader()) .getLoaded() .getDeclaredConstructor() .newInstance(); // 調(diào)用代理方法 proxy.save(new User(1L, "張三")); User user = proxy.find(2L); } }
優(yōu)點
- 流暢的API:提供鏈?zhǔn)骄幊田L(fēng)格,代碼更加可讀
- 性能卓越:在多項基準(zhǔn)測試中,性能優(yōu)于CGLIB和JDK代理
- 類型安全:API設(shè)計更注重類型安全,減少運(yùn)行時錯誤
- 支持Java新特性:對Java 9+模塊系統(tǒng)等新特性有更好的支持
- 功能豐富:支持方法重定向、字段訪問、構(gòu)造函數(shù)攔截等多種場景
局限性
- 額外依賴:需要引入額外的依賴庫
- 學(xué)習(xí)曲線:API雖然流暢,但概念較多,有一定學(xué)習(xí)成本
- 在Spring中集成度不高:需要自定義配置才能在Spring中替代默認(rèn)代理機(jī)制
Spring中的應(yīng)用
ByteBuddy雖然不是Spring默認(rèn)的代理實現(xiàn),但可以通過自定義ProxyFactory
來集成:
@Configuration public class ByteBuddyProxyConfig { @Bean public AopConfigurer byteBuddyAopConfigurer() { return new AopConfigurer() { @Override public void configureProxyCreator(Object bean, String beanName) { // 配置ByteBuddy作為代理創(chuàng)建器 // 實現(xiàn)細(xì)節(jié)略 } }; } }
4. Javassist:更易用的字節(jié)碼編輯庫
實現(xiàn)原理
Javassist是一個開源的Java字節(jié)碼操作庫,提供了兩個層次的API:源代碼級別和字節(jié)碼級別。它允許開發(fā)者用簡單的Java語法直接編輯字節(jié)碼,無需深入了解JVM規(guī)范。
核心代碼示例
public class JavassistProxyDemo { interface UserService { void save(User user); User find(Long id); } static class UserServiceImpl implements UserService { @Override public void save(User user) { System.out.println("保存用戶: " + user.getName()); } @Override public User find(Long id) { System.out.println("查找用戶ID: " + id); return new User(id, "用戶" + id); } } public static void main(String[] args) throws Exception { ProxyFactory factory = new ProxyFactory(); // 設(shè)置代理接口 factory.setInterfaces(new Class[] { UserService.class }); // 創(chuàng)建方法過濾器 MethodHandler handler = new MethodHandler() { private UserService target = new UserServiceImpl(); @Override public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable { System.out.println("Before: " + method.getName()); long startTime = System.currentTimeMillis(); Object result = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println("After: " + method.getName() + ", 耗時: " + (endTime - startTime) + "ms"); return result; } }; // 創(chuàng)建代理對象 UserService proxy = (UserService) factory.create(new Class<?>[0], new Object[0], handler); // 調(diào)用代理方法 proxy.save(new User(1L, "張三")); User user = proxy.find(2L); } }
優(yōu)點
- 源代碼級API:可以不懂字節(jié)碼指令集,以Java代碼形式操作字節(jié)碼
- 輕量級庫:相比其他字節(jié)碼庫體積較小
- 功能全面:支持創(chuàng)建類、修改類、動態(tài)編譯Java源代碼等
- 性能良好:生成的代理代碼性能接近CGLIB
- 長期維護(hù):庫歷史悠久,穩(wěn)定可靠
局限性
- API不夠直觀:部分API設(shè)計較為古老,使用不如ByteBuddy流暢
- 文檔不足:相比其他庫,文檔和示例較少
- 內(nèi)存消耗:在操作大量類時可能消耗較多內(nèi)存
Spring中的應(yīng)用
Javassist不是Spring默認(rèn)的代理實現(xiàn),但一些基于Spring的框架使用它實現(xiàn)動態(tài)代理,如Hibernate:
// 自定義Javassist代理工廠示例 public class JavassistProxyFactory implements ProxyFactory { @Override public Object createProxy(Object target, Interceptor interceptor) { // Javassist代理實現(xiàn) // ... } }
5. AspectJ:完整的AOP解決方案
實現(xiàn)原理
AspectJ是一個完整的AOP框架,與前面的動態(tài)代理方案不同,它提供兩種方式:
- 編譯時織入:在編譯源代碼時直接修改字節(jié)碼
- 加載時織入:在類加載到JVM時修改字節(jié)碼
AspectJ擁有專門的切面語言,功能比Spring AOP更強(qiáng)大。
核心代碼示例
AspectJ切面定義:
@Aspect public class LoggingAspect { @Before("execution(* com.example.service.UserService.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before: " + joinPoint.getSignature().getName()); } @Around("execution(* com.example.service.UserService.*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before: " + joinPoint.getSignature().getName()); long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); System.out.println("After: " + joinPoint.getSignature().getName() + ", 耗時: " + (endTime - startTime) + "ms"); return result; } }
Spring配置AspectJ:
@Configuration @EnableAspectJAutoProxy public class AspectJConfig { @Bean public LoggingAspect loggingAspect() { return new LoggingAspect(); } }
編譯時織入配置(maven):
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.14.0</version> <configuration> <complianceLevel>11</complianceLevel> <source>11</source> <target>11</target> <aspectLibraries> <aspectLibrary> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> </aspectLibraries> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin>
優(yōu)點
- 功能最全面:支持幾乎所有AOP場景,包括構(gòu)造函數(shù)、字段訪問、異常等
- 性能最優(yōu):編譯時織入無運(yùn)行時開銷,性能接近原生代碼
- 完整語言支持:有專門的切面語言和語法,表達(dá)能力強(qiáng)
- 更靈活的切入點:可以對接口、實現(xiàn)類、構(gòu)造函數(shù)、字段等定義切面
局限性
- 復(fù)雜度高:學(xué)習(xí)曲線陡峭,需要掌握AspectJ語法
- 構(gòu)建過程改變:需要特殊的編譯器或類加載器
- 調(diào)試難度增加:修改后的字節(jié)碼可能難以調(diào)試
Spring中的應(yīng)用
Spring可以通過以下方式使用AspectJ:
proxy-target-class模式:使用Spring AOP但CGLIB生成的代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
LTW(Load-Time Weaving)模式:在類加載時織入
@EnableLoadTimeWeaving
編譯時織入:需要配置AspectJ編譯器
使用場景對比與選擇建議
代理實現(xiàn) | 最適用場景 | 不適用場景 |
---|---|---|
JDK動態(tài)代理 | 基于接口的簡單代理,輕量級應(yīng)用 | 沒有實現(xiàn)接口的類,性能敏感場景 |
CGLIB | 沒有實現(xiàn)接口的類,需要兼顧性能和便捷性 | final類/方法,高安全性環(huán)境 |
ByteBuddy | 現(xiàn)代化項目,關(guān)注性能優(yōu)化,復(fù)雜代理邏輯 | 追求最小依賴的簡單項目 |
Javassist | 需要動態(tài)生成/修改類的復(fù)雜場景 | API設(shè)計敏感項目,初學(xué)者 |
AspectJ | 企業(yè)級應(yīng)用,性能關(guān)鍵型場景,復(fù)雜AOP需求 | 簡單項目,快速原型,學(xué)習(xí)成本敏感 |
到此這篇關(guān)于SpringBoot中5種動態(tài)代理的實現(xiàn)方案的文章就介紹到這了,更多相關(guān)SpringBoot動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Mybatis實現(xiàn)CRUD操作過程解析(xml方式)
這篇文章主要介紹了基于Mybatis實現(xiàn)CRUD操作過程解析(xml方式),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11一篇文章了解Jackson注解@JsonFormat及失效解決辦法
這篇文章主要給大家介紹了關(guān)于如何通過一篇文章了解Jackson注解@JsonFormat及失效解決辦法的相關(guān)資料,@JsonFormat注解是一個時間格式化注解,用于格式化時間,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11Springboot actuator應(yīng)用后臺監(jiān)控實現(xiàn)
這篇文章主要介紹了Springboot actuator應(yīng)用后臺監(jiān)控實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04Java實現(xiàn)將列表數(shù)據(jù)導(dǎo)出為PDF文件并添加水印
這篇文章主要為大家詳細(xì)介紹了如何使用Java實現(xiàn)把列表數(shù)據(jù)導(dǎo)出為PDF文件,同時加上PDF水印,文中的示例代碼講解詳細(xì),需要的可以參考下2024-02-02