深度解析Spring AOP @Aspect 原理、實(shí)戰(zhàn)與最佳實(shí)踐教程
1. @Aspect 核心概念
1.1 AOP 編程范式
- 核心思想:將橫切關(guān)注點(diǎn)(如日志、事務(wù)、安全)與業(yè)務(wù)邏輯分離
- 解決的問(wèn)題:避免代碼中出現(xiàn)大量重復(fù)的"模板代碼"(如每個(gè)Service方法都寫(xiě)事務(wù)控制)
1.2 @Aspect 關(guān)鍵特性
| 特性 | 說(shuō)明 |
|---|---|
| 基于注解 | 比傳統(tǒng)XML配置更簡(jiǎn)潔 |
| 代理機(jī)制 | 運(yùn)行時(shí)生成代理對(duì)象(JDK動(dòng)態(tài)代理/CGLIB) |
| 連接點(diǎn)模型 | 支持方法執(zhí)行、異常處理等多種切入點(diǎn) |
2. 完整代碼實(shí)現(xiàn)解析
2.1 基礎(chǔ)切面結(jié)構(gòu)
@Aspect
@Component
@Order(1) // 控制多個(gè)切面的執(zhí)行順序
@Slf4j
public class LoggingAspect {
// 定義可重用的切入點(diǎn)表達(dá)式
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void logMethodStart(JoinPoint jp) {
log.info("?? 調(diào)用 {}.{} 參數(shù): {}",
jp.getTarget().getClass().getSimpleName(),
jp.getSignature().getName(),
Arrays.toString(jp.getArgs()));
}
@Around("@annotation(com.example.audit.AuditLog)")
public Object auditLog(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
log.info("?? 審計(jì)日志 - 操作: {}, 耗時(shí): {}ms",
pjp.getSignature().getName(),
System.currentTimeMillis() - start);
return result;
}
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logException(JoinPoint jp, Exception ex) {
log.error("?? 方法 {} 拋出異常: {}",
jp.getSignature(), ex.getMessage());
}
}2.2 高級(jí)切面示例:接口限流
@Aspect
@Component
public class RateLimitAspect {
private final RateLimiter limiter = RateLimiter.create(100); // 100 QPS
@Around("@annotation(rateLimit)")
public Object limit(ProceedingJoinPoint pjp, RateLimit rateLimit)
throws Throwable {
if (!limiter.tryAcquire(rateLimit.timeout(), rateLimit.timeUnit())) {
throw new RateLimitException("請(qǐng)求過(guò)于頻繁");
}
return pjp.proceed();
}
}
// 自定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimit {
long timeout() default 1;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}3. 核心原理深度剖析
3.1 代理機(jī)制對(duì)比
| 代理類(lèi)型 | 條件 | 性能 | 限制 |
|---|---|---|---|
| JDK動(dòng)態(tài)代理 | 目標(biāo)實(shí)現(xiàn)接口 | 較高 | 只能代理接口方法 |
| CGLIB代理 | 無(wú)接口類(lèi) | 略低 | 無(wú)法代理final方法 |
代理選擇邏輯:
// Spring AbstractAutoProxyCreator 的核心邏輯
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return JdkDynamicAopProxy();
}
return ObjenesisCglibAopProxy();4. 生產(chǎn)環(huán)境最佳實(shí)踐
4.1 性能優(yōu)化方案
精確切入點(diǎn)匹配
// 不推薦(掃描范圍過(guò)大)
@Pointcut("execution(* *(..))")
// 推薦(限定包路徑+注解)
@Pointcut("execution(* com.yourpackage..service.*.*(..)) && " +
"@annotation(org.springframework.transaction.annotation.Transactional)")避免切面內(nèi)部耗時(shí)操作
@Around("serviceLayer()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
// 錯(cuò)誤示范:在切面內(nèi)進(jìn)行數(shù)據(jù)庫(kù)操作
// auditRepository.save(...);
// 正確做法:只做輕量級(jí)記錄
long start = System.nanoTime();
Object result = pjp.proceed();
long duration = System.nanoTime() - start;
metrics.record(duration);
return result;
}4.2 事務(wù)切面特殊處理
@Aspect
@Component
public class TransactionRetryAspect {
@Around("@annotation(retry)")
public Object retry(ProceedingJoinPoint pjp, RetryOnConflict retry)
throws Throwable {
int attempts = 0;
do {
try {
return pjp.proceed();
} catch (OptimisticLockingFailureException ex) {
if (++attempts >= retry.maxAttempts()) throw ex;
Thread.sleep(retry.backoff());
}
} while (true);
}
}5. 常見(jiàn)陷阱與解決方案
5.1 自調(diào)用問(wèn)題
public class OrderService {
public void createOrder() {
this.updateStock(); // 自調(diào)用不走代理!
}
@Transactional
public void updateStock() {...}
}解決方案:
通過(guò)ApplicationContext獲取代理對(duì)象
((OrderService) context.getBean("orderService")).updateStock();使用AopContext(需開(kāi)啟exposeProxy)
((OrderService) AopContext.currentProxy()).updateStock();
5.2 循環(huán)依賴(lài)問(wèn)題
現(xiàn)象:A切面依賴(lài)B服務(wù),B服務(wù)又需要被A切面代理
解決:
@DependsOn("bService") // 強(qiáng)制初始化順序
@Aspect
@Component
public class AAspect {
@Autowired
private BService bService;
}6. 監(jiān)控與調(diào)試技巧
6.1 查看生成的代理類(lèi)
# application.properties spring.aop.proxy-target-class=true # 強(qiáng)制使用CGLIB logging.level.org.springframework.aop=DEBUG
6.2 切面執(zhí)行監(jiān)控
@Aspect
@Component
public class AopMonitorAspect {
@Around("within(@org.aspectj.lang.annotation.Aspect *)")
public Object monitorAspect(ProceedingJoinPoint pjp) throws Throwable {
String aspectName = pjp.getTarget().getClass().getSimpleName();
Monitor.start("aop.aspect." + aspectName);
try {
return pjp.proceed();
} finally {
Monitor.end();
}
}
}7. 進(jìn)階:編譯時(shí)織入(AspectJ)
7.1 與Spring AOP對(duì)比
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 織入時(shí)機(jī) | 運(yùn)行時(shí) | 編譯期/類(lèi)加載期 |
| 性能 | 有代理開(kāi)銷(xiāo) | 無(wú)運(yùn)行時(shí)損耗 |
| 能力 | 僅方法級(jí)別 | 支持字段、構(gòu)造器等 |
7.2 配置示例(Maven插件)
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>總結(jié)
- 正確使用場(chǎng)景:日志、監(jiān)控、緩存等非核心但全局的需求
- 避免濫用:業(yè)務(wù)規(guī)則校驗(yàn)等核心邏輯應(yīng)放在Service層
- 性能關(guān)鍵點(diǎn):切入點(diǎn)表達(dá)式精度、切面內(nèi)部復(fù)雜度
- 推薦組合:Spring AOP + 編譯時(shí)織入(關(guān)鍵路徑)
到此這篇關(guān)于深度解析Spring AOP @Aspect 原理、實(shí)戰(zhàn)與最佳實(shí)踐教程的文章就介紹到這了,更多相關(guān)Spring AOP @Aspect 原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring中AOP注解@Aspect的使用詳解
- SpringAop @Aspect織入不生效,不執(zhí)行前置增強(qiáng)織入@Before方式
- Spring-AOP @AspectJ進(jìn)階之如何綁定代理對(duì)象
- Spring-AOP @AspectJ切點(diǎn)函數(shù)之@annotation()用法
- Spring AOP使用@Aspect注解 面向切面實(shí)現(xiàn)日志橫切的操作
- 基于Spring AOP @AspectJ進(jìn)階說(shuō)明
- 基于spring@aspect注解的aop實(shí)現(xiàn)過(guò)程代碼實(shí)例
- 談?wù)凷pring AOP中@Aspect的高級(jí)用法示例
相關(guān)文章
javacv-ffmpeg ProcessBuilder批量旋轉(zhuǎn)圖片方式
為了批量處理大量圖片的旋轉(zhuǎn),可以使用javacv-ffmpeg結(jié)合ProcessBuilder,首先在maven配置文件中添加ffmpeg及javacpp依賴(lài),javacpp支持調(diào)用C/C++方法,而ffmpeg基于C語(yǔ)言,使用ProcessBuilder創(chuàng)建進(jìn)程調(diào)用ffmpeg方法2024-09-09
Java基礎(chǔ)入門(mén) Swing中間容器的使用
這篇文章主要介紹了Java基礎(chǔ)入門(mén) Swing中間容器的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java使用itext5實(shí)現(xiàn)生成多個(gè)PDF并合并
這篇文章主要為大家詳細(xì)介紹了Java如何使用itext5實(shí)現(xiàn)生成多個(gè)PDF并合并,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
java基于QuartzJobBean實(shí)現(xiàn)定時(shí)功能的示例代碼
QuartzJobBean是Quartz框架中的一個(gè)抽象類(lèi),用于定義和實(shí)現(xiàn)可由Quartz調(diào)度的作業(yè),本文主要介紹了java基于QuartzJobBean實(shí)現(xiàn)定時(shí)功能的示例代碼,具有一定的參考價(jià)值,感興趣可以了解一下2023-09-09
SpringBoot+BootStrap多文件上傳到本地實(shí)例
這篇文章主要介紹了SpringBoot+BootStrap多文件上傳到本地實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
jquery對(duì)輸入框內(nèi)容的數(shù)字校驗(yàn)代碼實(shí)例
這篇文章主要介紹了jquery對(duì)輸入框內(nèi)容的數(shù)字校驗(yàn)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
Java MyBatis返回兩個(gè)字段作為Map的key和value問(wèn)題
使用MyBatis查詢(xún)兩個(gè)字段并返回Map時(shí),需要注意數(shù)據(jù)量和值的類(lèi)型,直接返回Map會(huì)導(dǎo)致報(bào)錯(cuò),使用@MapKey注解可以生成Map,但值是對(duì)象而不是直接值,為了解決這個(gè)問(wèn)題,可以自定義一個(gè)Map結(jié)果處理器MapResultHandler2024-12-12
java實(shí)現(xiàn)圖像轉(zhuǎn)碼為字符畫(huà)的方法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖像轉(zhuǎn)碼為字符畫(huà)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03

