SpringBoot中5種動(dòng)態(tài)代理的實(shí)現(xiàn)方案
動(dòng)態(tài)代理允許我們?cè)诓恍薷脑创a的情況下,為對(duì)象增加額外的行為。在SpringBoot應(yīng)用中,動(dòng)態(tài)代理被廣泛用于實(shí)現(xiàn)事務(wù)管理、緩存、安全控制、日志記錄等橫切關(guān)注點(diǎn)。
1. JDK動(dòng)態(tài)代理:Java原生的代理方案
實(shí)現(xiàn)原理
JDK動(dòng)態(tài)代理是Java標(biāo)準(zhǔn)庫(kù)提供的代理機(jī)制,基于java.lang.reflect.Proxy類和InvocationHandler接口實(shí)現(xiàn)。它通過反射在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建接口的代理實(shí)例。
核心代碼示例
public class JdkDynamicProxyDemo {
interface UserService {
void save(User user);
User find(Long id);
}
// 實(shí)現(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() + ", 耗時(shí): " + (endTime - startTime) + "ms");
return result;
}
}
public static void main(String[] args) {
// 創(chuàng)建目標(biāo)對(duì)象
UserService userService = new UserServiceImpl();
// 創(chuàng)建代理對(duì)象
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)點(diǎn)
- JDK標(biāo)準(zhǔn)庫(kù)自帶:無需引入額外依賴,減少了項(xiàng)目體積
- 生成代碼簡(jiǎn)單:代理邏輯集中在InvocationHandler中,易于理解和維護(hù)
- 性能相對(duì)穩(wěn)定:在JDK 8后的版本中,性能有明顯提升
局限性
- 只能代理接口:被代理類必須實(shí)現(xiàn)接口,無法代理類
- 反射調(diào)用開銷:每次方法調(diào)用都需通過反射機(jī)制,有一定性能損耗
- 無法攔截final方法:無法代理被final修飾的方法
Spring中的應(yīng)用
在Spring中,當(dāng)Bean實(shí)現(xiàn)了接口時(shí),默認(rèn)使用JDK動(dòng)態(tài)代理。這可以通過配置修改:
@EnableAspectJAutoProxy(proxyTargetClass = false) // 默認(rèn)值為false,表示優(yōu)先使用JDK動(dòng)態(tài)代理
2. CGLIB代理:基于字節(jié)碼的強(qiáng)大代理
實(shí)現(xiàn)原理
CGLIB(Code Generation Library)是一個(gè)強(qiáng)大的高性能字節(jié)碼生成庫(kù),它通過繼承被代理類生成子類的方式實(shí)現(xiàn)代理。Spring從3.2版本開始將CGLIB直接集成到框架中。
核心代碼示例
public class CglibProxyDemo {
// 不需要實(shí)現(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() + ", 耗時(shí): " + (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)建代理對(duì)象
UserService proxy = (UserService) enhancer.create();
// 調(diào)用代理方法
proxy.save(new User(1L, "張三"));
User user = proxy.find(2L);
}
}
優(yōu)點(diǎn)
- 可以代理類:不要求目標(biāo)類實(shí)現(xiàn)接口,應(yīng)用場(chǎ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)用:在生成代理對(duì)象時(shí)會(huì)調(diào)用目標(biāo)類的構(gòu)造函數(shù),可能導(dǎo)致意外行為
- 復(fù)雜性增加:生成的字節(jié)碼復(fù)雜度高,調(diào)試?yán)щy
- 對(duì)Java版本敏感:在不同Java版本間可能存在兼容性問題
Spring中的應(yīng)用
在Spring中,當(dāng)Bean沒有實(shí)現(xiàn)接口或配置了proxyTargetClass=true時(shí),使用CGLIB代理:
@EnableAspectJAutoProxy(proxyTargetClass = true) // 強(qiáng)制使用CGLIB代理
3. ByteBuddy:現(xiàn)代化的字節(jié)碼操作庫(kù)
實(shí)現(xiàn)原理
ByteBuddy是一個(gè)相對(duì)較新的字節(jié)碼生成和操作庫(kù),設(shè)計(jì)更加現(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() + ", 耗時(shí): " + (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)點(diǎn)
- 流暢的API:提供鏈?zhǔn)骄幊田L(fēng)格,代碼更加可讀
- 性能卓越:在多項(xiàng)基準(zhǔn)測(cè)試中,性能優(yōu)于CGLIB和JDK代理
- 類型安全:API設(shè)計(jì)更注重類型安全,減少運(yùn)行時(shí)錯(cuò)誤
- 支持Java新特性:對(duì)Java 9+模塊系統(tǒng)等新特性有更好的支持
- 功能豐富:支持方法重定向、字段訪問、構(gòu)造函數(shù)攔截等多種場(chǎng)景
局限性
- 額外依賴:需要引入額外的依賴庫(kù)
- 學(xué)習(xí)曲線:API雖然流暢,但概念較多,有一定學(xué)習(xí)成本
- 在Spring中集成度不高:需要自定義配置才能在Spring中替代默認(rèn)代理機(jī)制
Spring中的應(yīng)用
ByteBuddy雖然不是Spring默認(rèn)的代理實(shí)現(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)建器
// 實(shí)現(xiàn)細(xì)節(jié)略
}
};
}
}
4. Javassist:更易用的字節(jié)碼編輯庫(kù)
實(shí)現(xiàn)原理
Javassist是一個(gè)開源的Java字節(jié)碼操作庫(kù),提供了兩個(gè)層次的API:源代碼級(jí)別和字節(jié)碼級(jí)別。它允許開發(fā)者用簡(jiǎn)單的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() + ", 耗時(shí): " + (endTime - startTime) + "ms");
return result;
}
};
// 創(chuàng)建代理對(duì)象
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)點(diǎn)
- 源代碼級(jí)API:可以不懂字節(jié)碼指令集,以Java代碼形式操作字節(jié)碼
- 輕量級(jí)庫(kù):相比其他字節(jié)碼庫(kù)體積較小
- 功能全面:支持創(chuàng)建類、修改類、動(dòng)態(tài)編譯Java源代碼等
- 性能良好:生成的代理代碼性能接近CGLIB
- 長(zhǎng)期維護(hù):庫(kù)歷史悠久,穩(wěn)定可靠
局限性
- API不夠直觀:部分API設(shè)計(jì)較為古老,使用不如ByteBuddy流暢
- 文檔不足:相比其他庫(kù),文檔和示例較少
- 內(nèi)存消耗:在操作大量類時(shí)可能消耗較多內(nèi)存
Spring中的應(yīng)用
Javassist不是Spring默認(rèn)的代理實(shí)現(xiàn),但一些基于Spring的框架使用它實(shí)現(xiàn)動(dòng)態(tài)代理,如Hibernate:
// 自定義Javassist代理工廠示例
public class JavassistProxyFactory implements ProxyFactory {
@Override
public Object createProxy(Object target, Interceptor interceptor) {
// Javassist代理實(shí)現(xiàn)
// ...
}
}
5. AspectJ:完整的AOP解決方案
實(shí)現(xiàn)原理
AspectJ是一個(gè)完整的AOP框架,與前面的動(dòng)態(tài)代理方案不同,它提供兩種方式:
- 編譯時(shí)織入:在編譯源代碼時(shí)直接修改字節(jié)碼
- 加載時(shí)織入:在類加載到JVM時(shí)修改字節(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() +
", 耗時(shí): " + (endTime - startTime) + "ms");
return result;
}
}
Spring配置AspectJ:
@Configuration
@EnableAspectJAutoProxy
public class AspectJConfig {
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
}
編譯時(shí)織入配置(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)點(diǎn)
- 功能最全面:支持幾乎所有AOP場(chǎng)景,包括構(gòu)造函數(shù)、字段訪問、異常等
- 性能最優(yōu):編譯時(shí)織入無運(yùn)行時(shí)開銷,性能接近原生代碼
- 完整語言支持:有專門的切面語言和語法,表達(dá)能力強(qiáng)
- 更靈活的切入點(diǎn):可以對(duì)接口、實(shí)現(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)模式:在類加載時(shí)織入
@EnableLoadTimeWeaving
編譯時(shí)織入:需要配置AspectJ編譯器
使用場(chǎng)景對(duì)比與選擇建議
| 代理實(shí)現(xiàn) | 最適用場(chǎng)景 | 不適用場(chǎng)景 |
|---|---|---|
| JDK動(dòng)態(tài)代理 | 基于接口的簡(jiǎn)單代理,輕量級(jí)應(yīng)用 | 沒有實(shí)現(xiàn)接口的類,性能敏感場(chǎng)景 |
| CGLIB | 沒有實(shí)現(xiàn)接口的類,需要兼顧性能和便捷性 | final類/方法,高安全性環(huán)境 |
| ByteBuddy | 現(xiàn)代化項(xiàng)目,關(guān)注性能優(yōu)化,復(fù)雜代理邏輯 | 追求最小依賴的簡(jiǎn)單項(xiàng)目 |
| Javassist | 需要?jiǎng)討B(tài)生成/修改類的復(fù)雜場(chǎng)景 | API設(shè)計(jì)敏感項(xiàng)目,初學(xué)者 |
| AspectJ | 企業(yè)級(jí)應(yīng)用,性能關(guān)鍵型場(chǎng)景,復(fù)雜AOP需求 | 簡(jiǎn)單項(xiàng)目,快速原型,學(xué)習(xí)成本敏感 |
到此這篇關(guān)于SpringBoot中5種動(dòng)態(tài)代理的實(shí)現(xiàn)方案的文章就介紹到這了,更多相關(guān)SpringBoot動(dòng)態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Mybatis實(shí)現(xiàn)CRUD操作過程解析(xml方式)
這篇文章主要介紹了基于Mybatis實(shí)現(xiàn)CRUD操作過程解析(xml方式),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
Spring?Security過濾器鏈體系的實(shí)例詳解
這篇文章主要介紹了Spring?Security過濾器鏈體系,通過思維導(dǎo)圖可以很好的幫助大家理解配置類的相關(guān)知識(shí),結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02
一篇文章了解Jackson注解@JsonFormat及失效解決辦法
這篇文章主要給大家介紹了關(guān)于如何通過一篇文章了解Jackson注解@JsonFormat及失效解決辦法的相關(guān)資料,@JsonFormat注解是一個(gè)時(shí)間格式化注解,用于格式化時(shí)間,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
Springboot actuator應(yīng)用后臺(tái)監(jiān)控實(shí)現(xiàn)
這篇文章主要介紹了Springboot actuator應(yīng)用后臺(tái)監(jiān)控實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
Java實(shí)現(xiàn)將列表數(shù)據(jù)導(dǎo)出為PDF文件并添加水印
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)把列表數(shù)據(jù)導(dǎo)出為PDF文件,同時(shí)加上PDF水印,文中的示例代碼講解詳細(xì),需要的可以參考下2024-02-02
springSecurity實(shí)現(xiàn)簡(jiǎn)單的登錄功能
這篇文章主要為大家詳細(xì)介紹了springSecurity實(shí)現(xiàn)簡(jiǎn)單的登錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09

