AspectJ 5 引入註解式切面模型,讓任意 POJO 透過少量註解即可轉化為切面,無需學習 AspectJ 語言擴展語法。
傳統 XML 方式需在設定檔聲明切點與通知,類別本身無切面痕跡。@AspectJ 讓切面邏輯與程式碼共存,更直觀。
package com.sjf.bean;
public class Singer {
public void perform() {
System.out.println("正在上演個人演唱會... ");
}
}
package com.sjf.bean;
import org.aspectj.lang.annotation.*;
@Aspect
public class Audience {
// 定義切點:匹配 Singer.perform() 任意參數
@Pointcut("execution(* com.sjf.bean.Singer.perform(..))")
public void singerPerform() { /* 空方法,僅供註解依附 */ }
// 前置通知:表演前
@Before("singerPerform()")
public void takeSeats() {
System.out.println("the audience is taking their seats...");
}
// 返回後通知:表演成功
@AfterReturning("singerPerform()")
public void applaud() {
System.out.println("very good, clap clap clap...");
}
// 拋出異常通知:表演失敗
@AfterThrowing("singerPerform()")
public void demandRefund() {
System.out.println("very bad, We want our money back...");
}
}
@Aspect:標記此類為切面@Pointcut:定義可重用切點,值為 AspectJ 切點運算式,方法名即切點名@Before、@AfterReturning、@AfterThrowing:通知類型,參數引用切點名<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:context="https://www.springframework.org/schema/context"
xmlns:aop="https://www.springframework.org/schema/aop"
xsi:schemaLocation="
https://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-4.2.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="singer" class="com.sjf.bean.Singer"/>
<bean id="audience" class="com.sjf.bean.Audience"/>
<!-- 關鍵:啟用 @AspectJ 自動代理 -->
<aop:aspectj-autoproxy/>
</beans>
<aop:aspectj-autoproxy/> 內部建立 AnnotationAwareAspectJAutoProxyCreator,自動代理匹配切點的 Bean。
the audience is taking their seats...
正在上演個人演唱會...
very good, clap clap clap...
| 維度 | XML 宣告式 | @AspectJ 註解式 |
|---|---|---|
| 程式碼侵入性 | 零侵入(POJO 完全不知情) | 需標註類別與方法 |
| 原始碼需求 | 不需切面原始碼 | 需切面原始碼 |
| 可讀性 | 集中式配置,易管理 | 邏輯與程式碼共存,易理解 |
| 重用性 | 高(可套用至任意類別) | 中(綁定特定類別) |
@Around("singerPerform()")
public void performTime(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("the audience is taking their seats...");
long start = System.currentTimeMillis();
try {
joinPoint.proceed(); // 執行目標方法
long end = System.currentTimeMillis();
System.out.println("very good, clap clap clap...");
System.out.println("演出耗時 " + (end - start) + " ms");
} catch (Throwable e) {
System.out.println("very bad, We want our money back...");
throw e;
}
}
環繞通知必須接受 ProceedingJoinPoint 參數並呼叫 proceed() 執行目標方法。
// 目標方法新增參數
public void perform(String song) { ... }
// 切面定義切點並綁定參數
@Pointcut("execution(* com.sjf.bean.Singer.perform(String)) and args(song)")
public void singerPerform() {}
@Before("singerPerform() and args(song)")
public void beforeSong(String song) {
System.out.println("演唱歌曲為: " + song);
}
args(song) 將目標方法參數綁定至通知方法同名參數。
// 錯誤:切點方法帶參數
@Pointcut("execution(* com.sjf.bean.Singer.perform(String)) and args(song)")
public void singerPerform(String song) { } // ❌ 切點方法不可帶參數
// 正確:切點方法無參數,通知方法用 args 綁定
@Pointcut("execution(* com.sjf.bean.Singer.perform(String)) and args(song)")
public void singerPerform() { }
@Before("singerPerform() and args(song)")
public void beforeSong(String song) { } // ✅
來源:《Spring 實戰》