SpringBoot中5種動態(tài)代理的實現(xiàn)方案
動態(tài)代理允許我們在不修改源代碼的情況下,為對象增加額外的行為。在SpringBoot應用中,動態(tài)代理被廣泛用于實現(xiàn)事務管理、緩存、安全控制、日志記錄等橫切關注點。
1. JDK動態(tài)代理:Java原生的代理方案
實現(xiàn)原理
JDK動態(tài)代理是Java標準庫提供的代理機制,基于java.lang.reflect.Proxy類和InvocationHandler接口實現(xià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);
}
}
// 調用處理器
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)建目標對象
UserService userService = new UserServiceImpl();
// 創(chuàng)建代理對象
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LoggingInvocationHandler(userService)
);
// 調用代理方法
proxy.save(new User(1L, "張三"));
User user = proxy.find(2L);
}
}
優(yōu)點
- JDK標準庫自帶:無需引入額外依賴,減少了項目體積
- 生成代碼簡單:代理邏輯集中在InvocationHandler中,易于理解和維護
- 性能相對穩(wěn)定:在JDK 8后的版本中,性能有明顯提升
局限性
- 只能代理接口:被代理類必須實現(xiàn)接口,無法代理類
- 反射調用開銷:每次方法調用都需通過反射機制,有一定性能損耗
- 無法攔截final方法:無法代理被final修飾的方法
Spring中的應用
在Spring中,當Bean實現(xiàn)了接口時,默認使用JDK動態(tài)代理。這可以通過配置修改:
@EnableAspectJAutoProxy(proxyTargetClass = false) // 默認值為false,表示優(yōu)先使用JDK動態(tài)代理
2. CGLIB代理:基于字節(jié)碼的強大代理
實現(xiàn)原理
CGLIB(Code Generation Library)是一個強大的高性能字節(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();
// 調用原始方法
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();
// 調用代理方法
proxy.save(new User(1L, "張三"));
User user = proxy.find(2L);
}
}
優(yōu)點
- 可以代理類:不要求目標類實現(xiàn)接口,應用場景更廣泛
- 性能較高:通過生成字節(jié)碼而非反射調用,方法調用性能優(yōu)于JDK代理
- 功能豐富:支持多種回調類型,如LazyLoader、Dispatcher等
- 集成到Spring:Spring框架已內置CGLIB,無需額外依賴
局限性
- 無法代理final類和方法:由于使用繼承機制,無法代理final修飾的類或方法
- 構造函數(shù)調用:在生成代理對象時會調用目標類的構造函數(shù),可能導致意外行為
- 復雜性增加:生成的字節(jié)碼復雜度高,調試困難
- 對Java版本敏感:在不同Java版本間可能存在兼容性問題
Spring中的應用
在Spring中,當Bean沒有實現(xiàn)接口或配置了proxyTargetClass=true時,使用CGLIB代理:
@EnableAspectJAutoProxy(proxyTargetClass = true) // 強制使用CGLIB代理
3. ByteBuddy:現(xiàn)代化的字節(jié)碼操作庫
實現(xiàn)原理
ByteBuddy是一個相對較新的字節(jié)碼生成和操作庫,設計更加現(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();
// 調用代理方法
proxy.save(new User(1L, "張三"));
User user = proxy.find(2L);
}
}
優(yōu)點
- 流暢的API:提供鏈式編程風格,代碼更加可讀
- 性能卓越:在多項基準測試中,性能優(yōu)于CGLIB和JDK代理
- 類型安全:API設計更注重類型安全,減少運行時錯誤
- 支持Java新特性:對Java 9+模塊系統(tǒng)等新特性有更好的支持
- 功能豐富:支持方法重定向、字段訪問、構造函數(shù)攔截等多種場景
局限性
- 額外依賴:需要引入額外的依賴庫
- 學習曲線:API雖然流暢,但概念較多,有一定學習成本
- 在Spring中集成度不高:需要自定義配置才能在Spring中替代默認代理機制
Spring中的應用
ByteBuddy雖然不是Spring默認的代理實現(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)細節(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();
// 設置代理接口
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);
// 調用代理方法
proxy.save(new User(1L, "張三"));
User user = proxy.find(2L);
}
}
優(yōu)點
- 源代碼級API:可以不懂字節(jié)碼指令集,以Java代碼形式操作字節(jié)碼
- 輕量級庫:相比其他字節(jié)碼庫體積較小
- 功能全面:支持創(chuàng)建類、修改類、動態(tài)編譯Java源代碼等
- 性能良好:生成的代理代碼性能接近CGLIB
- 長期維護:庫歷史悠久,穩(wěn)定可靠
局限性
- API不夠直觀:部分API設計較為古老,使用不如ByteBuddy流暢
- 文檔不足:相比其他庫,文檔和示例較少
- 內存消耗:在操作大量類時可能消耗較多內存
Spring中的應用
Javassist不是Spring默認的代理實現(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更強大。
核心代碼示例
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場景,包括構造函數(shù)、字段訪問、異常等
- 性能最優(yōu):編譯時織入無運行時開銷,性能接近原生代碼
- 完整語言支持:有專門的切面語言和語法,表達能力強
- 更靈活的切入點:可以對接口、實現(xiàn)類、構造函數(shù)、字段等定義切面
局限性
- 復雜度高:學習曲線陡峭,需要掌握AspectJ語法
- 構建過程改變:需要特殊的編譯器或類加載器
- 調試難度增加:修改后的字節(jié)碼可能難以調試
Spring中的應用
Spring可以通過以下方式使用AspectJ:
proxy-target-class模式:使用Spring AOP但CGLIB生成的代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
LTW(Load-Time Weaving)模式:在類加載時織入
@EnableLoadTimeWeaving
編譯時織入:需要配置AspectJ編譯器
使用場景對比與選擇建議
| 代理實現(xiàn) | 最適用場景 | 不適用場景 |
|---|---|---|
| JDK動態(tài)代理 | 基于接口的簡單代理,輕量級應用 | 沒有實現(xiàn)接口的類,性能敏感場景 |
| CGLIB | 沒有實現(xiàn)接口的類,需要兼顧性能和便捷性 | final類/方法,高安全性環(huán)境 |
| ByteBuddy | 現(xiàn)代化項目,關注性能優(yōu)化,復雜代理邏輯 | 追求最小依賴的簡單項目 |
| Javassist | 需要動態(tài)生成/修改類的復雜場景 | API設計敏感項目,初學者 |
| AspectJ | 企業(yè)級應用,性能關鍵型場景,復雜AOP需求 | 簡單項目,快速原型,學習成本敏感 |
到此這篇關于SpringBoot中5種動態(tài)代理的實現(xiàn)方案的文章就介紹到這了,更多相關SpringBoot動態(tài)代理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Tk.mybatis零sql語句實現(xiàn)動態(tài)sql查詢的方法(4種)
有時候,查詢數(shù)據(jù)需要根據(jù)條件使用動態(tài)查詢,這時候需要使用動態(tài)sql,本文主要介紹了Tk.mybatis零sql語句實現(xiàn)動態(tài)sql查詢的方法,感興趣的可以了解一下2021-12-12
SpringBoot 中的 @Transactional 事務基本作用及注意事項示例詳解
@Transactional是SpringBoot中聲明式事務管理的核心注解,通過注解簡化事務控制,確保操作的原子性,支持異?;貪L、隔離級別和傳播行為配置,需注意代理模式、異常拋出及數(shù)據(jù)庫支持等條件,避免事務失效,本文介紹SpringBoot @Transactional事務作用,感興趣的朋友一起看看吧2025-09-09
springboot如何配置上傳文件的maxRequestSize
這篇文章主要介紹了springboot如何配置上傳文件的maxRequestSize,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring Boot中使用@Scheduled和Quartz實現(xiàn)定時任務的詳細過程
Spring Boot提供了兩種常見的定時任務實現(xiàn)方式,一種是基于@Scheduled注解的簡單定時任務,另一種是功能更強大的Quartz框架,本文將介紹如何在Spring Boot中使用這兩種方式實現(xiàn)定時任務和調度功能,感興趣的朋友一起看看吧2025-08-08
Java中FileOutputStream流的write方法
這篇文章主要為大家詳細介紹了Java中FileOutputStream流的write方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-08-08

