前言
本文主要跟大家分享介紹了關于spring aop中@aspect的高級用法,下面話不多說了,來隨著小編一起看看詳細的介紹吧。
1 切點復合運算
支持在切點定義中加入以下運算符進行復合運算:
運算符 | 說明 |
---|---|
&& | 與運算。 |
! | 非運算。 |
|| | 或運算。 |
2 切點命名
一般情況下,切點是直接聲明在需要增強方法處,這種切點的聲明方式稱為匿名切點,匿名切點只能在聲明處被使用 。 如果希望在其它地方可以重用這個切點,我們可以通過 @pointcut 注解及切面類方法來命名它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class namepointcut { /** * 切點被命名為 method1,且該切點只能在本類中使用 */ @pointcut ( "within(net.deniro.spring4.aspectj.*)" ) private void method1() { } /** * 切點被命名為 method2,且該切點可以在本類或子孫類中使用 */ @pointcut ( "within(net.deniro.spring4.aspectj.*)" ) protected void method2() { } /** * 切點被命名為 method3,且該切點可以在任何類中使用 * 這里還使用了復合運算 */ @pointcut ( "method1() && method2()" ) public void method3() { } } |
命名切點的結構如下:
切點可訪問性修飾符與類可訪問性修飾符的功能是相同的,它可以決定定義的切點可以在哪些類中可使用。
因為命名切點僅利用方法名及訪問修飾符的信息,所以我們一般定義方法的返回類型為 void ,并且方法體為空 。
定義好切點后,就可以在切面類中引用啦:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@aspect public class namepointcutaspect { @after ( "namepointcut.method2()" ) public void aspectmethod1() { } /** * 這里使用了復合運算 */ @after ( "namepointcut.method2() && namepointcut.method3()" ) public void aspectmethod2() { } } |
3 織入順序
一個連接點可以同時匹配多個切點,而切點所對應的增強在連接點上織入順序的規則是這樣的:
1.如果在同一個切面類中聲明的增強,則按照增強在切面類中定義的順序進行織入;
2.如果增強位于不同的切面類中,并且這些切面類都實現了org.springframework.core.ordered 接口,則由 ordered 方法的順序號決定(順序號小的先織入);
3.如果增強位于不同的切面類中,但這些切面類沒有實現org.springframework.core.ordered 接口,織入的順序是不確定的 。
假設有兩個切面類 a 與 b,它們都實現了 ordered 接口,a 的順序號為 1,b 的順序號為 2,切面類 a 與 b 都定義了 3 個增強,那么同時匹配這 6 個增強的織入順序如下圖所示:
4 獲取連接點信息
4.1 joinpoint
org.aspectj.lang.joinpoint 接口表示目標類連接點對象,它定義這些主要方法。
方法 | 說明 |
---|---|
object[] getargs() | 獲取連接點方法運行時的入參列表。 |
signature getsignature() | 獲取連接點的方法簽名對象。 |
object gettarget() | 獲取連接點所在的目標對象。 |
object getthis() | 獲取代理對象。 |
4.2 proceedingjoinpoint
org.aspectj.lang.proceedingjoinpoint 繼承了 joinpoint 接口,它新增了兩個方法(它們用于執行連接點方法)。
方法 | 說明 |
---|---|
object proceed() throws throwable | 通過反射執行目標對象連接點處的方法。 |
object proceed(object[] var1) throws throwable | 使用新的入參(替換掉原來的入參),通過反射執行目標對象連接點處的方法。 |
4.3 示例
cook 接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public interface cook { /** * 制作食品 */ void make(); /** * 制作 * * @param name 食品名稱 */ void make(string name); } |
cooka 類:
1
2
3
4
5
6
7
8
9
|
public class cooka implements cook { public void make() { system.out.println( "制作食品" ); } public void make(string name) { system.out.println( "制作" + name); } } |
在切面類中訪問連接點信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@aspect public class joinpointaspect { @around ( "within(net.deniro.spring4.aspectj.cooka)" ) public void test(proceedingjoinpoint pjp) throws throwable { system.out.println( "---------獲取連接點對象【開始】---------" ); system.out.println( "參數:" + pjp.getargs()[ 0 ]); system.out.println( "簽名對象:" + pjp.gettarget().getclass()); //執行目標對象方法 pjp.proceed(); system.out.println( "---------獲取連接點對象【結束】---------" ); } } |
spring bean 配置:
1
2
3
4
5
6
7
8
9
10
11
12
|
<?xml version= "1.0" encoding= "utf-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/xmlschema-instance" xmlns:aop= "http://www.springframework.org/schema/aop" xsi:schemalocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <!--aspectj 驅動器 --> <aop:aspectj-autoproxy/> <bean id= "cooka" class = "net.deniro.spring4.aspectj.cooka" /> <bean class = "net.deniro.spring4.aspectj.joinpointaspect" /> </beans> |
輸出結果:
---------獲取連接點對象【開始】---------
參數:壽司
簽名對象:class net.deniro.spring4.aspectj.cooka
制作壽司
---------獲取連接點對象【結束】---------
5 綁定連接點的方法入參
args()、this()、target()、@args()、@within()、@target() 和 @annotation() 這些切點函數除可以指定類名外,還可以指定參數名,將目標對象連接點上的方法入參綁定到增強的方法中 。 其中 args() 用于綁定連接點方法的入參, @annotation() 用于綁定連接點方法的注解對象,而 @args() 用于綁定連接點方法入參的注解。
cookc 類:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class cookc implements cook { public void make() { system.out.println( "制作食品" ); } public void make(string name) { system.out.println( "制作" + name); } public void make(string name, int num) { system.out.println( "制作" + name + " " + num + " 個" ); } } |
切面類:
1
2
3
4
5
6
7
8
9
10
11
12
|
@aspect public class paramsaspect { @before ( "target(net.deniro.spring4.aspectj.cookc) && args(name,num,..)" ) public void test(string name, int num) { system.out.println( "----------綁定連接點入參【開始】----------" ); system.out.println( "name:" + name); system.out.println( "num:" + num); system.out.println( "----------綁定連接點入參【結束】----------" ); } } |
- 這里的連接點表達式 args(name,num,..) 會先找到 name 與 num 的類型,從而生成真正的表達式 args(string,int,..)。
- 增強方法可以通過 name 與 num 得到連接點的方法入參。
切點匹配和參數綁定的過程是這樣的:
- args()會根據參數名稱在增強方法中查到名稱相同的入參并獲得對應參數的類型,這樣就得到了匹配連接點方法的入參類型 。
- 連接點方法入參類型所在的位置由參數名在 args() 函數中聲明的位置決定 。
上述示例中的匹配過程如下:
spring 配置:
1
2
3
4
5
|
<!--aspectj 驅動器 --> <aop:aspectj-autoproxy proxy-target- class = "true" /> <bean id= "cookc" class = "net.deniro.spring4.aspectj.cookc" /> <bean class = "net.deniro.spring4.aspectj.paramsaspect" /> |
注意: 這里必須通過 <aop:aspectj-autoproxy proxy-target-class="true" />來啟用 cglib 動態代理,這是因為 cookc 的 public void make(string name, int num) 是該類獨有的方法(非接口定義的方法),所以必須使用 cglib 生成子類的代理方法 。
單元測試:
1
2
3
|
applicationcontext context = new classpathxmlapplicationcontext( "spring-beans.xml" ); cookc cookc = (cookc) context.getbean( "cookc" ); cookc.make( "壽司" , 100 ); |
輸出結果:
----------綁定連接點入參【開始】----------
name:壽司
num:100
----------綁定連接點入參【結束】----------
制作壽司 100 個
6 綁定代理對象
使用 this() 或 target() 可綁定被代理對象的實例。通過類實例名綁定對象時,依然具有原來連接點匹配的功能,只是類名是由增強方法中的同名入參類型間接決定的。
1
2
3
4
5
6
7
8
9
10
|
@aspect public class proxyaspect { @before ( "this(cook)" ) public void bind(cook cook) { system.out.println( "--------綁定代理對象【開始】--------" ); system.out.println(cook.getclass().getname()); system.out.println( "--------綁定代理對象【結束】--------" ); } } |
首先通過 public void bind(cook cook) 找出 cook 所對應的類型,接著轉換切點表達式為 this(net.deniro.spring4.aspectj.cook) 。這樣就表示該切點匹配所有代理對象為 cook 類中的所有方法。
輸出結果:
--------綁定代理對象【開始】--------
net.deniro.spring4.aspectj.cookc$$enhancerbyspringcglib$$217fb793
--------綁定代理對象【結束】--------
target() 綁定與 this() 相似。
7 綁定類注解對象
@within() 和 @target() 函數都可以將目標類的注解對象綁定到增強方法中。
定義一個日志注解類:
1
2
3
4
5
|
@retention (retentionpolicy.runtime) //保留期限 @target ({elementtype.method,elementtype.type}) //目標類型 public @interface log { boolean value() default true ; //聲明成員變量 } |
把該注解類應用于 cookd:
1
2
3
4
5
6
7
8
9
10
|
@log public class cookd implements cook { public void make() { system.out.println( "制作糕點" ); } public void make(string name) { } } |
綁定類注解對象:
1
2
3
4
5
6
7
8
9
10
|
@aspect public class classannotationobjectaspect { @before ( "@within(log)" ) public void bind(log log){ system.out.println( "----------綁定類注解對象【開始】----------" ); system.out.println(log.getclass().getname()); system.out.println( "----------綁定類注解對象【結束】----------" ); } } |
spring 配置:
1
2
3
4
5
6
|
<!--aspectj 驅動器 --> <aop:aspectj-autoproxy proxy-target- class = "true" /> <bean id= "cookd" class = "net.deniro.spring4.aspectj.cookd" /> <bean class = "net.deniro.spring4.aspectj.classannotationobjectaspect" /> |
單元測試:
1
2
3
|
applicationcontext context = new classpathxmlapplicationcontext( "spring-beans.xml" ); cookd cook = (cookd) context.getbean( "cookd" ); cook.make(); |
輸出結果:
----------綁定類注解對象【開始】----------
com.sun.proxy.$proxy8
----------綁定類注解對象【結束】----------
從輸出結果 com.sun.proxy.$proxy8 可以看出 ,cookd 類的注解 log 對象也被代理咯o(∩_∩)o哈哈~
8 綁定返回值
在后置增強中,可以通過 returning 來綁定連接點方法的返回值。
切面:
1
2
3
4
5
6
7
8
9
10
|
@aspect public class returnvalueaspect { @afterreturning (value = "target(net.deniro.spring4.aspectj.cooka)" , returning = "value" ) public void bind( boolean value) { system.out.println( "綁定返回值【開始】" ); system.out.println( "value:" + value); system.out.println( "綁定返回值【結束】" ); } } |
注意:returning 的值必須與方法參數名相同。
cooka 新增 smell 方法:
1
2
3
4
|
public boolean smell(string name) { system.out.println(name + "香嗎?" ); return true ; } |
單元測試:
1
2
3
|
applicationcontext context = new classpathxmlapplicationcontext( "spring-beans.xml" ); cooka cook = (cooka) context.getbean( "cooka" ); cook.smell( "烤鴨" ); |
輸出結果:
烤鴨香嗎?
綁定返回值【開始】
value:true
綁定返回值【結束】
9 綁定異常
可以使用 afterthrowing 注解的 throwing 成員變量來綁定連接點拋出的異常。
切面類:
1
2
3
4
5
6
7
8
9
10
|
@aspect public class exceptionaspect { @afterthrowing (value = "target(net.deniro.spring4.aspectj.cooka)" ,throwing = "e" ) public void bind(cookexception e){ system.out.println( "綁定異常【開始】" ); system.out.println( "e:" + e.getmessage()); system.out.println( "綁定異常【結束】" ); } } |
注意:throwing 的值必須與方法參數名相同。
單元測試:
1
2
3
|
applicationcontext context = new classpathxmlapplicationcontext( "spring-beans.xml" ); cooka cook = (cooka) context.getbean( "cooka" ); cook.make( "" ); |
輸出結果:
綁定異常【開始】
e:煮啥呢???
綁定異常【結束】
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.jianshu.com/p/0d7cf0aeb338