AOP (Aspect-Oriented Programming) 是 OOP 的有力補充:OOP 是縱向繼承體系,AOP 是橫向切面抽取。將日誌、權限、事務等橫切關注點從業務邏輯中分離,封裝為可復用的切面,降低耦合、提升維護性。
| 術語 | 說明 | 對應註解/配置 |
|---|---|---|
| Aspect (切面) | 橫切邏輯的模組化封裝 | @Aspect / <aop:aspect> |
| Join Point (連接點) | 程式執行過程中的某個具體點 (方法呼叫、異常拋出等) | — |
| Pointcut (切入點) | 匹配一組連接點的條件表達式 | @Pointcut / <aop:pointcut> |
| Advice (通知/增強) | 在切入點執行的具體動作 | 前置/後置/環繞/例外/返回 |
| Weaving (織入) | 將切面應用到目標物件的過程 | 編譯期/類載入期/執行期 |
<!-- 切面 Bean -->
<bean id="serviceAspect" class="cn.ysh.studio.spring.aop.aspect.ServiceAspect"/>
<aop:config>
<aop:aspect id="simpleAspect" ref="serviceAspect">
<!-- 切入點:service 包下所有類的所有方法 -->
<aop:pointcut expression="execution(* cn.ysh.studio.spring.aop.service..*(..))" id="simplePointcut"/>
<aop:before pointcut-ref="simplePointcut" method="before"/>
<aop:after pointcut-ref="simplePointcut" method="after"/>
<aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
<aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
</aop:aspect>
</aop:config>
啟用掃描與自動代理:
<context:component-scan base-package="cn.ysh.studio.spring.aop"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
切面類實作:
@Component
@Aspect
public class ServiceAspect {
@Pointcut("execution(* cn.ysh.studio.spring.aop.service..*(..))")
public void aspect() {}
@Before("aspect()")
public void before(JoinPoint jp) { /* 前置邏輯 */ }
@After("aspect()")
public void after() { /* 後置邏輯 (finally) */ }
@Around("aspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
/* 環繞:可控制是否執行目標方法、修改參數/返回值 */
return pjp.proceed();
}
@AfterReturning("aspect()")
public void afterReturn(Object result) { /* 正常返回後 */ }
@AfterThrowing(pointcut="aspect()", throwing="ex")
public void afterThrow(Exception ex) { /* 拋出異常後 */ }
}
| 通知類型 | 執行時機 | 典型用途 | 能否阻止目標執行 |
|---|---|---|---|
| @Before | 目標方法前 | 權限檢查、參數驗證 | 否 (拋異常可中斷) |
| @After | 目標方法後 (無論正常/異常) | 資源清理、日誌記錄 | 否 |
| @Around | 包裹目標方法 | 事務控制、效能監控、快取 | 是 (不呼叫 proceed) |
| @AfterReturning | 正常返回後 | 返回值處理、快取存入 | 否 |
| @AfterThrowing | 拋出異常後 | 異常日誌、告警通知 | 否 |
@Around 功能最強但也最複雜,務必呼叫 pjp.proceed() 否則目標方法不會執行@Transactional,而非手寫 AOP