1 @annotation()
它表示標註了某個註解的所有方法。
假設有一個介面 Cook,它有兩個實現類 CookA、CookB:
Cook:
public interface Cook {
/**
* 製作食品
*/
void make();
}
複製程式碼
CookA:
public class CookA implements Cook {
public void make() {
System.out.println("製作食品");
}
}
複製程式碼
CookB:
public class CookB implements Cook {
@Log
public void make() {
System.out.println("製作糕點");
}
}
複製程式碼
我們在 CookB 的 make() 方法上加了 @Log 註解。
@Log 註解:
@Retention(RetentionPolicy.RUNTIME)//保留期限
@Target(ElementType.METHOD)//目標型別
public @interface Log {
boolean value() default true;//宣告成員變數
}
複製程式碼
現在使用 @annotation() 來為所有加了 @Log 註解的方法織入增強:
@Aspect
public class AnnotationAspect {
@AfterReturning("@annotation(net.deniro.spring4.anno.Log)")
public void log() {
System.out.println("新增日誌");
}
}
複製程式碼
單元測試:
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Cook cookA = (Cook) context.getBean("cookA");
cookA.make();
Cook cookB = (Cook) context.getBean("cookB");
cookB.make();
複製程式碼
輸出結果:
製作食品 製作糕點 新增日誌
從輸出結果中可以看出,配置了 @Log 註解的方法織入了增強。
2 execution()
它是最常用的切點函式,具體語法如下:
execution(<修飾符模式>?<返回型別模式><方法名模式>(<引數模式>)<異常模式>?)
假設廚師介面 Cook,存在下面這樣的類關係:
CookA 除了會做菜與管理外,還會清潔的技能;CookB 也具有稽核的獨門技能。
2.1 方法簽名定義切點
表示式示例 | 說明 |
---|---|
execution(public * *(..)) |
表示匹配所有目標類的 public 方法 。 第一個 * 代表任意返回型別,第二個 * 代表方法名,而 .. 代表任意入參的方法。 |
execution(* m*(..)) | 表示匹配目標類所有以 m 為字首的方法。 第一個* 代表任意返回型別,而m* 代表任意以 m 為字首的方法。這裡匹配 make() 與 manage() 方法。 |
2.2 類定義切點
表示式示例 | 說明 |
---|---|
execution(* net.deniro.spring4.aspectj.Cook.*(..)) |
匹配 Cook 介面的所有方法,即這裡的 make() 與 manage() 方法。第一個* 代表任意返回型別, net.deniro.spring4.aspectj.Cook.* 表示 Cook 介面的所有方法。 |
execution(* net.deniro.spring4.aspectj.Cook+.*(..)) |
匹配 Cook 介面及其所有實現類的方法,它不但匹配介面中定義的 make() 與 manage() 方法,還匹配它們各自的 clean() 與 audit() 方法。 |
2.3 類包定義切點
類名模式 | 說明 |
---|---|
.* |
包中的所有類,不包括子孫包中的類。 |
..* |
包中以及子孫包的所有類。 |
表示式示例 | 說明 |
---|---|
execution(* net.deniro.*(..)) |
匹配 net.deniro 包中所有類的所有方法。 |
execution(* net.deniro..*(..)) |
匹配 net.deniro 包以及子孫包中所有類的所有方法。 |
2.4 方法入參定義切點
引數中的萬用字元 | 說明 |
---|---|
* |
表示任意型別引數。 |
.. |
表示任意型別引數且引數不限個數。 |
表示式示例 | 說明 |
---|---|
execution(* make(int,String)) |
這會匹配 make(int,String) 方法。如果方法中的入參型別是 java.lang 包下的類,那麼可以直接使用類名;如果是其他型別,那麼必須使用全限定名。 |
execution(* make(int,*)) |
匹配 make() 方法,該方法的第一個入參是 int,第二個引數可以是任意型別。 |
execution(* make(int,..)) |
匹配 make() 方法,該方法的第一個入參是 int,後面可以有任意型別的入參且引數個數不限。 |
execution(* make(Object+)) |
匹配 make() 方法,方法只有一個入參,它可以是 Object 型別或該型別的子類。 |
3 args() 與 @args()
3.1 args()
它接收一個類名,表示目前方法的入參物件(執行時)是指定類或子類時,就匹配切點。
表示式示例 | 說明 |
---|---|
args(net.deniro.CookA) |
表示執行時入參型別為 CookA。 |
execution(* *(net.deniro.CookA)) |
表示方法簽名中的入參型別為 CookA。 |
3.2 @args()
它接收一個註解類的類名,當方法的執行時入參物件標註了指定註解時,就匹配切點。
假設存在如下繼承結構的類關係:
CookB 的方法簽名為 method(CookB cook)
,我們稱之為入參型別點。標註了@args(Log)
註解的類,我們稱之為註解點。
- 如果註解點高於入參型別點,那麼入參型別點的類及其子孫類,都不匹配該切點
@args(Log)
:
- 如果註解點低於入參型別點,那麼入參型別點的子孫類,都匹配該切點
@args(Log)
:
4 within()
within() 定義的連線點最小範圍是類(不是介面),而 execution() 定義的連線點最小範圍可以到方法入參,所以 execution() 涵蓋了 within() 功能。
within() 語法:within (<類匹配模式>)
表示式示例 | 說明 |
---|---|
within(net.deniro.CookA) |
匹配目標類 CookA 中的所有方法。 |
within(net.deniro.*) |
匹配 net.deniro 包中所有的類,但不包括子孫包中的類。 |
within(net.deniro..*) |
匹配 net.deniro 包及其子孫包中所有的類。 |
5 @within() 與 @target()
這兩個註解的入參只能是類名(非介面)。
@within(Log)
:匹配標註了@Log
的類及其子孫類。@target(Log)
:匹配標註了@Log
的類。
示例中的 CookB 標註了 @Log
註解,那麼 @target(Log)
只匹配 CookB;而 @within(Log)
匹配 CookB 及其子孫類(示例中的 CookC 與 CookD)。
6 target() 與 this()
target() 會根據目標類的型別來確定是否匹配; this() 會根據代理類的型別來確定是否匹配。它們都僅接受類名作為引數。
6.1 target()
target(C) 表示如果目標類的型別是 C時,那麼目標類的所有方法都匹配切點。
表示式示例 | 說明 |
---|---|
target(net.deniro.Cook) |
Cook 的所有實現類及其子孫類中的所有方法(包括未在 Cook 介面中定義的方法)都匹配切點。 |
target(net.deniro.Cook+) |
與 target(net.deniro.Cook) 等價。 |
6.2 this()
一般來說, this() 與 target() 是等效的。但在由引介切面產生的代理物件場景中,卻表現不同。
我們定義了一個引介切面,讓 Cook 擁有 Designer 的技能 O(∩_∩)O哈哈~
Designer 介面:
public interface Designer {
//設計
void design();
}
複製程式碼
DefaultDesigner 實現類:
public class DefaultDesigner implements Designer {
public void design() {
System.out.println("開始設計啦");
}
}
複製程式碼
DesignerAspect 切面:
@Aspect
public class DesignerAspect {
@DeclareParents(value = "net.deniro.spring4.aspectj.CookA",defaultImpl =
DefaultDesigner.class)
public static Designer designer;
}
複製程式碼
接著,再定義了一個使用 this 語法的後置增強切面:
@Aspect
public class ThisAspect {
/**
* 為任何執行期物件為 Designer 型別的 Bean 織入後置增強
*/
@AfterReturning("this(net.deniro.spring4.aspectj.Designer)")
public void test() {
System.out.println("執行 ThisAspect");
}
}
複製程式碼
Spring 配置:
<!--aspectj 驅動器 -->
<aop:aspectj-autoproxy/>
<bean id="cookA" class="net.deniro.spring4.aspectj.CookA"/>
<bean class="net.deniro.spring4.aspectj.DesignerAspect"/>
<bean class="net.deniro.spring4.aspectj.ThisAspect"/>
複製程式碼
輸出結果:
製作食品 執行 ThisAspect 開始設計啦
因為 CookA 被織入了 Designer 介面,所以複合 this 定義的切面,所以 ThisAspect 中的方法被執行啦O(∩_∩)O哈哈~
CookA 通過引介增強了 Designer 介面,那麼 this(net.deniro.spring4.aspectj.Designer)
會匹配 CookA 中的所有方法以及 Designer 介面定義的方法。而 target(net.deniro.spring4.aspectj.Designer)
只會匹配Designer 介面定義的方法。