Pointcut 表達式是 AOP 的核心,我將詳細解析最常用的 execution
表達式,并介紹其他幾種同樣非常有用的表達式。
1. execution
指示符 (最常用,最強大)
execution
用于匹配方法的執行(Join Point)。它的語法結構最為完整,也最為靈活。
語法結構
execution( [修飾符模式]? 返回類型模式 [聲明類型模式]? 方法名模式(參數模式) [拋出異常模式]? )
- 方括號
[]
內的部分是可選的。 ?
表示該部分是可選的。
語法分解與示例
讓我們通過一個具體的例子來逐一分解:
execution(public * com.example.service..*.set*(..))
部分 | 示例 | 含義 |
---|---|---|
[修飾符模式] | public | 匹配 public 修飾的方法。可以省略,表示匹配任何修飾符。也可以用 * 。 |
返回類型模式 | * | 匹配任意返回類型。也可以是具體的類型如 String 、void 。 |
[聲明類型模式] | com.example.service..*. | 匹配方法所在的類型。這部分非常靈活: - com.example.service.UserService :精確匹配 UserService 類。- com.example.service.* :匹配 service 包下的任意直接類。- com.example.service..* :匹配 service 包及其所有子包下的任意類。最后的 . 是必需的。 |
方法名模式 | set* | 匹配以 set 開頭的方法名。也可以是精確的方法名 setName 或通配符 * 。 |
(參數模式) | (..) | 匹配任意數量、任意類型的參數。這是最常用的模式。 - () :匹配無參方法。- (*) :匹配只有一個任意類型參數的方法。- (String, ..) :匹配第一個參數是 String ,后面有任意數量、任意類型的參數。 |
[拋出異常模式] | (省略) | 匹配方法聲明拋出的異常類型。例如 throws java.io.IOException 。這部分很少使用。 |
這個例子的完整含義是:
攔截 com.example.service
包及其所有子包中,所有類的,所有以 set
開頭的,public
修飾的,不限返回值和參數的方法。
2. 其他常用 Pointcut 指示符
除了 execution
,還有一些在特定場景下非常好用的指示符。
within
指示符
within
用于限定連接點必須在指定的類型或包的范圍內。它比 execution
更粗粒度,只關心位置,不關心方法簽名。
-
語法:
within(類型或包模式)
-
示例:
within(com.example.service.UserService)
: 匹配UserService
類中的所有方法。within(com.example.service.*)
: 匹配service
包下所有類中的所有方法。within(com.example.service..*)
: 匹配service
包及其所有子包下所有類中的所有方法。
-
與
execution
的區別:execution(* com.example.service.UserService.*(..))
和within(com.example.service.UserService)
看起來相似,但within
的粒度更大。例如,within
不關心方法的修飾符、返回類型等。
@annotation
指示符
@annotation
用于匹配持有指定注解的方法。這對于創建自定義注解來實現 AOP 非常有用,是實現聲明式功能(如聲明式日志、聲明式緩存)的最佳方式。
-
語法:
@annotation(注解類型的完全限定名)
-
示例:
- 定義一個自定義注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyLoggable {}
- 在方法上使用注解:
@Service public class MyService {@MyLoggablepublic void doSomething() { /* ... */ } }
- 編寫切點:
這個切點會精確地匹配所有被@Pointcut("@annotation(com.example.aop.MyLoggable)") public void loggableMethods() {}
@MyLoggable
注解的方法。
- 定義一個自定義注解:
@within
和 @target
-
@within
: 匹配持有指定注解的類中的所有方法。- 示例:
@within(org.springframework.stereotype.Service)
會匹配所有被@Service
注解的類中的所有方法。
- 示例:
-
@target
: 匹配目標對象的類持有指定注解的所有方法。@within
和@target
在大多數情況下效果相同。
args
和 @args
這兩個指示符用于根據方法的參數來匹配。
-
args()
: 匹配方法的參數類型。args(java.lang.String, ..)
: 匹配第一個參數是String
類型的方法。args()
: 匹配無參方法。args(user)
: 這種形式不僅匹配參數類型,還能在通知中綁定參數值。@Before("execution(* com.example..*.*(com.example.User)) && args(user)") public void processUser(User user) {//可以直接使用 user 對象System.out.println("Processing user: " + user.getName()); }
-
@args()
: 匹配方法的參數在運行時所持有的注解。@args(com.example.validation.Validatable, ..)
: 匹配第一個參數對象被@Validatable
注解標注的方法。
3. 組合切點 (Combining Pointcuts)
你可以使用邏輯運算符 &&
(與), ||
(或), !
(非) 來組合多個切點,創建更復雜的規則。
@Aspect
@Component
public class SecurityAspect {// 匹配 service 包下的所有方法@Pointcut("within(com.example.service..*)")public void inServiceLayer() {}// 匹配被 @AdminOnly 注解的方法@Pointcut("@annotation(com.example.security.AdminOnly)")public void adminOnlyMethods() {}// 組合切點:在 service 層中,并且是 admin-only 的方法@Before("inServiceLayer() && adminOnlyMethods()")public void checkAdminAccess() {// ... 執行權限檢查邏輯System.out.println("Admin access granted!");}// 組合切點:不在 service 層中的所有方法@Before("!inServiceLayer()")public void nonServiceMethodLog() {// ...}
}
總結
指示符 | 作用 | 優點 |
---|---|---|
execution | 最核心、最常用,匹配方法的完整簽名。 | 最精確,控制粒度最細。 |
within | 匹配指定包或類中的所有方法。 | 語法簡單,適合按模塊劃分。 |
@annotation | 匹配持有指定注解的方法。 | 解耦性最好,通過注解驅動AOP,業務代碼無侵入。 |
@within , @target | 匹配持有指定注解的類中的所有方法。 | 適合對整個類別的Bean進行增強。 |
args , @args | 根據方法的參數類型或參數注解來匹配。 | 適合處理特定類型的數據流。 |
**&& , ` | , !`** |
在實際開發中,execution
和 @annotation
是使用頻率最高的兩種方式。建議優先使用 @annotation
來實現自定義的聲明式功能,因為它能讓我們的代碼意圖更加清晰。