一、AOP的基礎
1.1、AOP是什么???
考慮這樣一個問題:需要對系統中的某些業務做日志記錄,比如支付系統中的支付業務需要記錄支付相關日志,對于支付系統可能相當復雜,比如可能有自己的支付系統,也可能引入第三方支付平臺,面對這樣的支付系統該如何解決呢?
傳統解決方案:
2)支付部分,定義IPayService接口并定義支付方法“pay”,并定義了兩個實現:“PointPayService”表示積分支付,“RMBPayService”表示人民幣支付;并且在每個支付實現中支付邏輯和記錄日志:
3)支付實現很明顯有重復代碼,這個重復很明顯可以使用模板設計模式消除重復:
4)到此我們設計了一個可以復用的接口;但大家覺得這樣記錄日志會很好嗎,有沒有更好的解決方案?
如果對積分支付方式添加統計功能,比如在支付時記錄下用戶總積分數、當前消費的積分數,那我們該如何做呢?直接修改源代碼添加日志記錄,這完全違背了面向對象最重要的原則之一:開閉原則(對擴展開放,對修改關閉)
更好的解決方案:在我們的支付組件中由于使用了日志組件,即日志模塊橫切于支付組件,在傳統程序設計中很難將日志組件分離出來,即不耦合我們的支付組件;因此面向方面編程AOP就誕生了,它能分離我們的組件,使組件完全不耦合:
1)采用面向方面編程后,我們的支付組件看起來如下所示,代碼中不再有日志組件的任何東西;
2)所以日志相關的提取到一個切面中,AOP實現者會在合適的時候將日志功能織入到我們的支付組件中去,從而完全解耦支付組件和日志組件。
看到這大家可能不是很理解,沒關系,先往下看。
面向方面編程(AOP):也可稱為面向切面編程,是一種編程范式,提供從另一個角度來考慮程序結構從而完善面向對象編程(OOP)。
在進行OOP開發時,都是基于對組件(比如類)進行開發,然后對組件進行組合,OOP最大問題就是無法解耦組件進行開發,比如我們上邊舉例,而AOP就是為了克服這個問題而出現的,它來進行這種耦合的分離。
AOP為開發者提供一種進行橫切關注點(比如日志關注點橫切了支付關注點)分離并織入的機制,把橫切關注點分離,然后通過某種技術織入到系統中,從而無耦合的完成了我們的功能。
1.2、 能干什么????
AOP主要用于橫切關注點分離和織入,因此需要理解橫切關注點和織入:
- 關注點:可以認為是所關注的任何東西,比如上邊的支付組件;
- 關注點分離:將問題細化從而單獨部分,即可以理解為不可再分割的組件,如上邊的日志組件和支付組件;
- 橫切關注點:一個組件無法完成需要的功能,需要其他組件協作完成,如日志組件橫切于支付組件;
-
織入:橫切關注點分離后,需要通過某種技術將橫切關注點融合到系統中從而完成需要的功能,因此需要織入,織入可能在編譯期、加載期、運行期等進行。
橫切關注點可能包含很多,比如非業務的:日志、事務處理、緩存、性能統計、權限控制等等這些非業務的基礎功能;還可能是業務的:如某個業務組件橫切于多個模塊。如圖
image.png
傳統支付形式,流水方式:
image.png
面向切面方式,先將橫切關注點分離,再將橫切關注點織入到支付系統中:
image.png
AOP能干什么:
- 用于橫切關注點的分離和織入橫切關注點到系統;比如上邊提到的日志等等;
- 完善OOP;
- 降低組件和模塊之間的耦合性;
- 使系統容易擴展;
- 而且由于關注點分離從而可以獲得組件的更好復用。
1.3、AOP的基本概念
在進行AOP開發前,先熟悉幾個概念:
- 連接點(Jointpoint):表示需要在程序中插入橫切關注點的擴展點,連接點可能是類初始化、方法執行、方法調用、字段調用或處理異常等等,Spring只支持方法執行連接點,在AOP中表示為“在哪里干”;
- 切入點(Pointcut):選擇一組相關連接點的模式,即可以認為連接點的集合,Spring支持perl5正則表達式和AspectJ切入點模式,Spring默認使用AspectJ語法,在AOP中表示為“在哪里干的集合”;
- 通知(Advice):在連接點上執行的行為,通知提供了在AOP中需要在切入點所選擇的連接點處進行擴展現有行為的手段;包括前置通知(before advice)、后置通知(after advice)、環繞通知(around advice),在Spring中通過代理模式實現AOP,并通過攔截器模式以環繞連接點的攔截器鏈織入通知;在AOP中表示為“干什么”;
- 方面/切面(Aspect):橫切關注點的模塊化,比如上邊提到的日志組件。可以認?為是通知、引入和切入點的組合;在Spring中可以使用Schema和@AspectJ方式進行組織實現;在AOP中表示為“在哪干和干什么集合”;
- 引入(inter-type declaration):也稱為內部類型聲明,為已有的類添加額外新的字段或方法,Spring允許引入新的接口(必須對應一個實現)到所有被代理對象(目標對象), 在AOP中表示為“干什么(引入什么)”;
- 目標對象(Target Object):需要被織入橫切關注點的對象,即該對象是切入點選擇的對象,需要被通知的對象,從而也可稱為“被通知對象”;由于Spring AOP 通過代理模式實現,從而這個對象永遠是被代理對象,在AOP中表示為“對誰干”;
- AOP代理(AOP Proxy):AOP框架使用代理模式創建的對象,從而實現在連接點處插入通知(即應用切面),就是通過代理來對目標對象應用切面。在Spring中,AOP代理可以用JDK動態代理或CGLIB代理實現,而通過攔截器模型應用切面。
-
織入(Weaving):織入是一個過程,是將切面應用到目標對象從而創建出AOP代理對象的過程,織入可以在編譯期、類裝載期、運行期進行。
在AOP中,通過切入點選擇目標對象的連接點,然后在目標對象的相應連接點處織入通知,而切入點和通知就是切面(橫切關注點),而在目標對象連接點處應用切面的實現方式是通過AOP代理對象,如圖
image.png
接下來再讓我們具體看看Spring有哪些通知類型: - 前置通知(Before Advice):在切入點選擇的連接點處的方法之前執行的通知,該通知不影響正常程序執行流程(除非該通知拋出異常,該異常將中斷當前方法鏈的執行而返回)。
-
后置通知(After Advice): 在切入點選擇的連接點處的方法之后執行的通知,包括如下類型的后置通知:
- 后置返回通知(After returning Advice):在切入點選擇的連接點處的方法正常執行完畢時執行的通知,必須是連接點處的方法沒拋出任何異常正常返回時才調用后置通知。
- 后置異常通知(After throwing Advice): 在切入點選擇的連接點處的方法拋出異常返回時執行的通知,必須是連接點處的方法拋出任何異常返回時才調用異常通知。
- 后置最終通知(After finally Advice): 在切入點選擇的連接點處的方法返回時執行的通知,不管拋沒拋出異常都執行,類似于Java中的finally塊。
-
環繞通知(Around Advices):環繞著在切入點選擇的連接點處的方法所執行的通知,環繞通知可以在方法調用之前和之后自定義任何行為,并且可以決定是否執行連接點處的方法、替換返回值、拋出異常等等。
各種通知類型在UML序列圖中的位置如圖
image.png
1.4、 AOP代理
AOP代理就是AOP框架通過代理模式創建的對象,Spring使用JDK動態代理或CGLIB代理來實現,Spring缺省使用JDK動態代理來實現,從而任何接口都可別代理,如果被代理的對象實現不是接口將默認使用CGLIB代理,不過CGLIB代理當然也可應用到接口。
AOP代理的目的就是將切面織入到目標對象。
概念都將完了,接下來讓我們看一下AOP的 HelloWorld!吧。
二、AOP的HelloWorld(例子,對上面概念的一個使用)
2.1、準備環境
首先準備開發需要的jar包,請到spring-framework-3.0.5.RELEASE-dependencies.zip和spring-framework-3.0.5.RELEASE-with-docs中查找如下jar包:
org.springframework.aop-3.0.5.RELEASE.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.net.sf.cglib-2.2.0.jar
將這些jar包添加到“Build Path”下。
2.2、定義目標類
1)定義目標接口:
package com.nieshenkuan.aop;
/**
* 定義目標接口:創建被代理的接口
* @author NSK
*
*/
public interface IHelloWorldService {
void sayHello();
}
2)定義目標接口實現:
package com.nieshenkuan.aop;
/**
* 定義目標接口實現:接口的實現類
* @author NSK
*
*/
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
}
注:在日常開發中最后將業務邏輯定義在一個專門的service包下,而實現定義在service包下的impl包中,服務接口以IXXXService形式,而服務實現就是XXXServiceImpl,這就是規約設計,見名知義。當然可以使用公司內部更好的形式,只要大家都好理解就可以了。
2.2、 定義切面支持類
有了目標類,該定義切面了,切面就是通知和切入點的組合,而切面是通過配置方式定義的,因此這定義切面前,我們需要定義切面支持類,切面支持類提供了通知實現:
package com.nieshenkuan.aop;
/**
* 定義切面支持類 有了目標類,該定義切面了,切面就是通知和切入點的組合,而切面是通過配置方式定義的,
* 因此這定義切面前,我們需要定義切面支持類,切面支持類提供了通知實現
*
* @author NSK
*
*/
public class HelloWorldAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("===========before advice");
}
// 后置最終通知
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
}
此處HelloWorldAspect類不是真正的切面實現,只是定義了通知實現的類,在此我們可以把它看作就是缺少了切入點的切面。
注:對于AOP相關類最后專門放到一個包下,如“aop”包,因為AOP是動態織入的,所以如果某個目標類被AOP攔截了并應用了通知,可能很難發現這個通知實現在哪個包里,因此推薦使用規約命名,方便以后維護人員查找相應的AOP實現。
2.3 、在XML中進行配置
有了通知實現,那就讓我們來配置切面吧:
1)首先配置AOP需要aop命名空間,配置頭如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
</beans>
2)配置目標類:
<!-- 2)配置目標類: -->
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
3)配置切面:
<!-- 3)配置切面 -->
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.nieshenkuan..*.*(..))" />
<aop:aspect ref="aspect">
<aop:before pointcut-ref="pointcut" method="beforeAdvice" />
<aop:after pointcut="execution(* com.nieshenkuan..*.*(..))"
method="afterFinallyAdvice" />
</aop:aspect>
</aop:config>
整體配置:applicationContext2.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 2)配置目標類: -->
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<!-- 3)配置切面 -->
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.nieshenkuan..*.*(..))" />
<aop:aspect ref="aspect">
<aop:before pointcut-ref="pointcut" method="beforeAdvice" />
<aop:after pointcut="execution(* com.nieshenkuan..*.*(..))"
method="afterFinallyAdvice" />
</aop:aspect>
</aop:config>
</beans>
切入點使用<aop:config>標簽下的<aop:pointcut>配置,expression屬性用于定義切入點模式,默認是AspectJ語法,“execution(* com.nieshenkuan...(..))”表示匹配com.nieshenkuan包及子包下的任何方法執行。
切面使用<aop:config>標簽下的<aop:aspect>標簽配置,其中“ref”用來引用切面支持類的方法。
前置通知使用<aop:aspect>標簽下的<aop:before>標簽來定義,pointcut-ref屬性用于引用切入點Bean,而method用來引用切面通知實現類中的方法,該方法就是通知實現,即在目標類方法執行之前調用的方法。
最終通知使用<aop:aspect>標簽下的<aop:after >標簽來定義,切入點除了使用pointcut-ref屬性來引用已經存在的切入點,也可以使用pointcut屬性來定義,如pointcut="execution(* com.nieshenkuan...(..))",method屬性同樣是指定通知實現,即在目標類方法執行之后調用的方法。
2.4、運行測試
測試類非常簡單,調用被代理Bean跟調用普通Bean完全一樣,Spring AOP將為目標對象創建AOP代理,具體測試代碼如下:
package com.nieshenkuan.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void testAop() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext2.xml");
IHelloWorldService helloworldService = ac.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayHello();
}
}
該測試將輸出如下如下內容:
===========before advice
============Hello World!
===========after finally advice
從輸出我們可以看出:前置通知在切入點選擇的連接點(方法)之前允許,而后置通知將在連接點(方法)之后執行,具體生成AOP代理及執行過程如圖
三、 基于Schema的AOP
基于Schema的AOP從Spring2.0之后通過“aop”命名空間來定義切面、切入點及聲明通知。
在Spring配置文件中,所以AOP相關定義必須放在<aop:config>標簽下,該標簽下可以有<aop:pointcut>、<aop:advisor>、<aop:aspect>標簽,配置順序不可變。
- <aop:pointcut>:用來定義切入點,該切入點可以重用;
- <aop:advisor>:用來定義只有一個通知和一個切入點的切面;
- <aop:aspect>:用來定義切面,該切面可以包含多個切入點和通知,而且標簽內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。
<aop:config> | AOP定義開始(有序) |
---|---|
<aop:pointcut/> | 切入點定義(零個或多個) |
<aop:advisor/> | Advisor定義(零個或多個) |
<aop:aspect> | 切面定義開始(零個或多個,無序) |
<aop:pointcut/> | 切入點定義(零個或多個) |
<aop:before"/> | 前置通知(零個或多個) |
<aop:after-returning/> | 后置返回通知(零個或多個) |
<aop:after-throwing/> | 后置異常通知(零個或多個) |
<aop:after/> | 后置最終通知(零個或多個) |
<aop:around/> | 環繞通知(零個或多個) |
<aop:declare-parents/> | 引入定義(零個或多個) |
</aop:aspect> | 切面定義開始(零個或多個) |
</aop:config> | AOP定義結束 |
3.1、聲明切面
切面就是包含切入點和通知的對象,在Spring容器中將被定義為一個Bean,Schema方式的切面需要一個切面支持Bean,該支持Bean的字段和方法提供了切面的狀態和行為信息,并通過配置方式來指定切入點和通知實現。
切面使用<aop:aspect>標簽指定,ref屬性用來引用切面支持Bean。
<bean id="aspectSupportBean" class="……"/>
<aop:config>
<aop:aspect id="aspectId" ref="aspectSupportBean">
……
</aop:aspect>
</aop:config>
切面支持Bean“aspectSupportBean”跟普通Bean完全一樣使用,切面使用“ref”屬性引用它。
3.2 、 聲明切入點
切入點在Spring中也是一個Bean,Bean定義方式可以有很三種方式:
1)在<aop:config>標簽下使用<aop:pointcut>聲明一個切入點Bean,該切入點可以被多個切面使用,對于需要共享使用的切入點最好使用該方式,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表達式:
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.nieshenkuan..*.*(..))"/>
<aop:aspect ref="aspectSupportBean">
<aop:before pointcut-ref="pointcut" method="before"/>
</aop:aspect>
</aop:config>
2)在<aop:aspect>標簽下使用<aop:pointcut>聲明一個切入點Bean,該切入點可以被多個切面使用,但一般該切入點只被該切面使用,當然也可以被其他切面使用,但最好不要那樣使用,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表達式:
<aop:config>
<aop:aspect ref="aspectSupportBean">
<aop:pointcut id=" pointcut" expression="execution(* com.nieshenkuan..*.*(..))"/>
<aop:before pointcut-ref="pointcut" method="before"/>
</aop:aspect>
</aop:config>
3)匿名切入點Bean,可以在聲明通知時通過pointcut屬性指定切入點表達式,該切入點是匿名切入點,只被該通知使用:
<aop:config>
<aop:aspect ref="aspectSupportBean">
<aop:after pointcut="execution(* com.nieshenkuan..*.*(..))"
method="afterFinallyAdvice"/>
</aop:aspect>
</aop:config>
3.3 、聲明通知
基于Schema方式支持前邊介紹的5種通知類型:
一、前置通知:在切入點選擇的方法之前執行,通過<aop:aspect>標簽下的<aop:before>標簽聲明:
<aop:before pointcut="切入點表達式" pointcut-ref="切入點Bean引用"
method="前置通知實現方法名"
arg-names="前置通知實現方法參數列表參數名字"/>
- pointcut和pointcut-ref:二者選一,指定切入點;
- method:指定前置通知實現方法名,如果是多態需要加上參數類型,多個用“,”隔開,如beforeAdvice(java.lang.String);
-
arg-names:指定通知實現方法的參數名字,多個用“,”分隔,可選,類似于構造器注入中的參數名注入限制:在class文件中沒生成變量調試信息是獲取不到方法參數名字的,因此只有在類沒生成變量調試信息時才需要使用arg-names屬性來指定參數名,如arg-names="param"表示通知實現方法的參數列表的第一個參數名字為“param”。
首先在com.nieshenkuan.aop.IHelloWorldService定義一個測試方法:
package com.nieshenkuan.aop;
/**
* 定義目標接口:創建被代理的接口
*
* @author NSK
*
*/
public interface IHelloWorldService {
void sayHello();
// 新添加的方法=================================================
void sayBefore(String param);
// 新添加的方法=================================================
}
其次在com.nieshenkuan.aop. HelloWorldService定義實現:
package com.nieshenkuan.aop;
/**
* 定義目標接口實現:接口的實現類
*
* @author NSK
*
*/
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
// 新添加的方法=================================================
@Override
public void sayBefore(String param) {
System.out.println("============say " + param);
}
// 新添加的方法=================================================
}
第三在com.nieshenkuan.aop. HelloWorldAspect定義通知實現:
package com.nieshenkuan.aop;
/**
* 定義切面支持類 有了目標類,該定義切面了,切面就是通知和切入點的組合,而切面是通過配置方式定義的,
* 因此這定義切面前,我們需要定義切面支持類,切面支持類提供了通知實現
*
* @author NSK
*
*/
public class HelloWorldAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("===========before advice");
}
// 后置最終通知
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
// 新添加的方法=====================================
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
// 新添加的方法=================================================
}
最后在applicationContext3.xml配置文件中進行如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<aop:config>
<aop:aspect ref="aspect">
<aop:before
pointcut="execution(* com.nieshenkuan..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)" arg-names="param" />
</aop:aspect>
</aop:config>
</beans>
測試代碼Test.java
// ===============================================================
@org.junit.Test
public void estSchemaBeforeAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext3.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayBefore("before");
System.out.println("======================================");
}
// =================================================================
輸出為:
======================================
===========before advice param:before
============say before
======================================
分析一下吧:
1)切入點匹配:在配置中使用“execution(* cn.javass..*.sayBefore(..)) ”匹配目標方法sayBefore,且使用“args(param)”匹配目標方法只有一個參數且傳入的參數類型為通知實現方法中同名的參數類型;
2)目標方法定義:使用method=" beforeAdvice(java.lang.String) "指定前置通知實現方法,且該通知有一個參數類型為java.lang.String參數;
3)目標方法參數命名:其中使用arg-names=" param "指定通知實現方法參數名為“param”,切入點中使用“args(param)”匹配的目標方法參數將自動傳遞給通知實現方法同名參數。
二、后置返回通知:在切入點選擇的方法正常返回時執行,通過<aop:aspect>標簽下的<aop:after-returning>標簽聲明:
<aop:after-returning pointcut="切入點表達式" pointcut-ref="切入點Bean引用"
method="后置返回通知實現方法名"
arg-names="后置返回通知實現方法參數列表參數名字"
returning="返回值對應的后置返回通知實現方法參數名"
/>
- pointcut和pointcut-ref:同前置通知同義;
- method:同前置通知同義;
- arg-names:同前置通知同義;
-
returning:定義一個名字,該名字用于匹配通知實現方法的一個參數名,當目標方法執行正常返回后,將把目標方法返回值傳給通知方法;returning限定了只有目標方法返回值匹配與通知方法相應參數類型時才能執行后置返回通知,否則不執行,對于returning對應的通知方法參數為Object類型將匹配任何目標返回值。
首先在com.nieshenkuan.aop.IHelloWorldService定義一個測試方法(后置通知):
package com.nieshenkuan.aop;
/**
* 定義目標接口:創建被代理的接口
*
* @author NSK
*
*/
public interface IHelloWorldService {
void sayHello();
// 新添加的方法=================================================
void sayBefore(String param);
// 新添加的方法=================================================
// 新添加的方法,后置通知=============================
boolean sayAfterReturning();
// 新添加的方法,后置通知=============================
}
其次在com.nieshenkuan.aop. HelloWorldService定義實現:
package com.nieshenkuan.aop;
/**
* 定義目標接口實現:接口的實現類
*
* @author NSK
*
*/
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
// 新添加的方法=================================================
@Override
public void sayBefore(String param) {
System.out.println("============say " + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=============================
@Override
public boolean sayAfterReturning() {
System.out.println("============after returning");
return true;
}
// 新添加的方法,后置通知=============================
}
第三在com.nieshenkuan.aop. HelloWorldAspect定義通知實現(后置通知):
/**
* 定義切面支持類 有了目標類,該定義切面了,切面就是通知和切入點的組合,而切面是通過配置方式定義的,
* 因此這定義切面前,我們需要定義切面支持類,切面支持類提供了通知實現
*
* @author NSK
*
*/
public class HelloWorldAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("===========before advice");
}
// 后置最終通知
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
// 新添加的方法=====================================
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=================================================
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
}
// 新添加的方法,后置通知=================================================
}
最后在applicationContext4.xml配置文件中接著前置通知配置的例子添加如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<aop:config>
<aop:aspect ref="aspect">
<!-- 前置通知 -->
<aop:before
pointcut="execution(* com.nieshenkuan..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)" arg-names="param" />
<!-- 后置通知============================== -->
<aop:after-returning
pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
<!-- 后置通知============================== -->
</aop:aspect>
</aop:config>
</beans>
測試代碼Test.java
/**
* 后置通知
*
*/
// ===============================================================
@org.junit.Test
public void testSchemaAfterReturningAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAfterReturning();
System.out.println("======================================");
}
// =================================================================
結果:
======================================
============after returning
===========after returning advice retVal:true
======================================
分析一下吧:
1)切入點匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAfterReturning(..)) ”匹配目標方法sayAfterReturning,該方法返回true;
2)目標方法定義:使用method="afterReturningAdvice"指定后置返回通知實現方法;
3)目標方法參數命名:其中使用arg-names="retVal"指定通知實現方法參數名為“retVal”;
4)返回值命名:returning="retVal"用于將目標返回值賦值給通知實現方法參數名為“retVal”的參數上。
三、后置異常通知:在切入點選擇的方法拋出異常時執行,通過<aop:aspect>標簽下的<aop:after-throwing>標簽聲明:
<aop:after-throwing pointcut="切入點表達式" pointcut-ref="切入點Bean引用"
method="后置異常通知實現方法名"
arg-names="后置異常通知實現方法參數列表參數名字"
throwing="將拋出的異常賦值給的通知實現方法參數名"/>
- pointcut和pointcut-ref:同前置通知同義;
- method:同前置通知同義;
- arg-names:同前置通知同義;
- throwing:定義一個名字,該名字用于匹配通知實現方法的一個參數名,當目標方法拋出異常返回后,將把目標方法拋出的異常傳給通知方法;throwing限定了只有目標方法拋出的異常匹配與通知方法相應參數異常類型時才能執行后置異常通知,否則不執行,對于throwing對應的通知方法參數為Throwable類型將匹配任何異常。
首先在com.nieshenkuan.aop.IHelloWorldService定義一個測試方法(后置異常通知):
package com.nieshenkuan.aop;
/**
* 定義目標接口:創建被代理的接口
*
* @author NSK
*
*/
public interface IHelloWorldService {
void sayHello();
// 新添加的方法,前置通知=================================================
void sayBefore(String param);
// 新添加的方法,前置通知=================================================
// 新添加的方法,后置通知=============================
boolean sayAfterReturning();
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
void sayAfterThrowing();
// 新添加的方法,后置異常通知=============================
}
其次在com.nieshenkuan.aop. HelloWorldService定義實現:
/**
* 定義目標接口實現:接口的實現類
*
* @author NSK
*
*/
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
// 新添加的方法=================================================
@Override
public void sayBefore(String param) {
System.out.println("============say " + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=============================
@Override
public boolean sayAfterReturning() {
System.out.println("============after returning");
return true;
}
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
@Override
public void sayAfterThrowing() {
System.out.println("============before throwing");
throw new RuntimeException();
}
// 新添加的方法,后置異常通知=============================
}
第三在com.nieshenkuan.aop. HelloWorldAspect定義通知實現:
/**
* 定義切面支持類 有了目標類,該定義切面了,切面就是通知和切入點的組合,而切面是通過配置方式定義的,
* 因此這定義切面前,我們需要定義切面支持類,切面支持類提供了通知實現
*
* @author NSK
*
*/
public class HelloWorldAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("===========before advice");
}
// 后置最終通知
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
// 新添加的方法=====================================
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=================================================
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
}
// 新添加的方法,后置通知=================================================
// 新添加的方法,后置異常通知=============================
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
// 新添加的方法,后置異常通知=============================
}
最后在applicationContext4.xml配置文件中接著前置通知配置的例子添加如下配置:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<aop:config>
<aop:aspect ref="aspect">
<!-- 前置通知 -->
<aop:before
pointcut="execution(* com.nieshenkuan..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)" arg-names="param" />
<!-- 后置異常通知================================= -->
<aop:after-throwing
pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
<!-- 后置異常通知================================= -->
<!-- 后置通知============================== -->
<aop:after-returning
pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
<!-- 后置通知============================== -->
</aop:aspect>
</aop:config>
</beans>
測試代碼Test.java
/**
* 后置異常通知
*
*/
@org.junit.Test(expected = RuntimeException.class)
public void testSchemaAfterThrowingAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAfterThrowing();
System.out.println("======================================");
}
輸出:
============before throwing
===========after throwing advice exception:java.lang.RuntimeException
分析一下吧:
1)切入點匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAfterThrowing(..))”匹配目標方法sayAfterThrowing,該方法將拋出RuntimeException異常;
2)目標方法定義:使用method="afterThrowingAdvice"指定后置異常通知實現方法;
3)目標方法參數命名:其中使用arg-names="exception"指定通知實現方法參數名為“exception”;
4)異常命名:returning="exception"用于將目標方法拋出的異常賦值給通知實現方法參數名為“exception”的參數上。
四、后置最終通知:在切入點選擇的方法返回時執行,不管是正常返回還是拋出異常都執行,通過<aop:aspect>標簽下的<aop:after >標簽聲明:
<aop:after pointcut="切入點表達式" pointcut-ref="切入點Bean引用"
method="后置最終通知實現方法名"
arg-names="后置最終通知實現方法參數列表參數名字"/>
pointcut和pointcut-ref:同前置通知同義;
method:同前置通知同義;
arg-names:同前置通知同義;
首先在com.nieshenkuan.aop.IHelloWorldService定義一個測試方法(后置最終通知):
package com.nieshenkuan.aop;
/**
* 定義目標接口:創建被代理的接口
*
* @author NSK
*
*/
public interface IHelloWorldService {
void sayHello();
// 新添加的方法,前置通知=================================================
void sayBefore(String param);
// 新添加的方法,前置通知=================================================
// 新添加的方法,后置通知=============================
boolean sayAfterReturning();
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
void sayAfterThrowing();
// 新添加的方法,后置異常通知=============================
// 新添加的方法,后置最終通知=============================
boolean sayAfterFinally();
// 新添加的方法,后置最終通知=============================
}
其次在com.nieshenkuan.aop. HelloWorldService定義實現:
package com.nieshenkuan.aop;
/**
* 定義目標接口實現:接口的實現類
*
* @author NSK
*
*/
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
// 新添加的方法=================================================
@Override
public void sayBefore(String param) {
System.out.println("============say " + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=============================
@Override
public boolean sayAfterReturning() {
System.out.println("============after returning");
return true;
}
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
@Override
public void sayAfterThrowing() {
System.out.println("============before throwing");
throw new RuntimeException();
}
// 新添加的方法,后置異常通知=============================
// 新添加的方法,后置最終通知=============================
@Override
public boolean sayAfterFinally() {
System.out.println("============before finally");
throw new RuntimeException();
}
// 新添加的方法,后置最終通知=============================
}
第三在com.nieshenkuan.aop. HelloWorldAspect定義通知實現:
package com.nieshenkuan.aop;
/**
* 定義切面支持類 有了目標類,該定義切面了,切面就是通知和切入點的組合,而切面是通過配置方式定義的,
* 因此這定義切面前,我們需要定義切面支持類,切面支持類提供了通知實現
*
* @author NSK
*
*/
public class HelloWorldAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("===========before advice");
}
// 后置最終通知
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
// 新添加的方法=====================================
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=================================================
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
}
// 新添加的方法,后置通知=================================================
// 新添加的方法,后置異常通知=============================
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
// 新添加的方法,后置異常通知=============================
}
最后在applicationContext4.xml配置文件中接著前置通知配置的例子添加如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<aop:config>
<aop:aspect ref="aspect">
<!-- 前置通知 -->
<aop:before
pointcut="execution(* com.nieshenkuan..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)" arg-names="param" />
<!-- 后置異常通知================================= -->
<aop:after-throwing
pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
<!-- 后置異常通知================================= -->
<!-- 后置通知============================== -->
<aop:after-returning
pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
<!-- 后置通知============================== -->
<!-- 后置最終通知========================== -->
<aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
method="afterFinallyAdvice" />
<!-- 后置最終通知 ======================== -->
</aop:aspect>
</aop:config>
</beans>
測試代碼Test.java
/**
* 后置最終通知
*/
@org.junit.Test(expected = RuntimeException.class)
public void testSchemaAfterFinallyAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAfterFinally();
System.out.println("======================================");
}
結果:
============before finally
===========after finally advice
分析一下吧:
1)切入點匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAfterFinally(..))”匹配目標方法sayAfterFinally,該方法將拋出RuntimeException異常;
2)目標方法定義:使用method=" afterFinallyAdvice "指定后置最終通知實現方法。
五、環繞通知:環繞著在切入點選擇的連接點處的方法所執行的通知,環繞通知非常強大,可以決定目標方法是否執行,什么時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換返回值,可通過<aop:aspect>標簽下的<aop:around >標簽聲明:
<aop:around pointcut="切入點表達式" pointcut-ref="切入點Bean引用"
method="后置最終通知實現方法名"
arg-names="后置最終通知實現方法參數列表參數名字"/>
- pointcut和pointcut-ref:同前置通知同義;
- method:同前置通知同義;
-
arg-names:同前置通知同義;
環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型,在通知實現方法內部使用ProceedingJoinPoint的proceed()方法使目標方法執行,proceed 方法可以傳入可選的Object[]數組,該數組的值將被作為目標方法執行時的參數。
首先在com.nieshenkuan.aop.IHelloWorldService定義一個測試方法(環繞通知):
package com.nieshenkuan.aop;
/**
* 定義目標接口:創建被代理的接口
*
* @author NSK
*
*/
public interface IHelloWorldService {
void sayHello();
// 新添加的方法,前置通知=================================================
void sayBefore(String param);
// 新添加的方法,前置通知=================================================
// 新添加的方法,后置通知=============================
boolean sayAfterReturning();
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
void sayAfterThrowing();
// 新添加的方法,后置異常通知=============================
// 新添加的方法,后置最終通知=============================
boolean sayAfterFinally();
// 新添加的方法,后置最終通知=============================
// 新添加的方法,環繞通知=============================
void sayAround(String param);
// 新添加的方法,環繞通知=============================
}
其次在com.nieshenkuan.aop. HelloWorldService定義實現:
package com.nieshenkuan.aop;
/**
* 定義目標接口實現:接口的實現類
*
* @author NSK
*
*/
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
// 新添加的方法=================================================
@Override
public void sayBefore(String param) {
System.out.println("============say " + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=============================
@Override
public boolean sayAfterReturning() {
System.out.println("============after returning");
return true;
}
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
@Override
public void sayAfterThrowing() {
System.out.println("============before throwing");
throw new RuntimeException();
}
// 新添加的方法,后置異常通知=============================
// 新添加的方法,后置最終通知=============================
@Override
public boolean sayAfterFinally() {
System.out.println("============before finally");
throw new RuntimeException();
}
// 新添加的方法,后置最終通知=============================
// 新添加的方法,環繞通知=============================
@Override
public void sayAround(String param) {
System.out.println("============around param:" + param);
}
// 新添加的方法,環繞通知=============================
}
第三在com.nieshenkuan.aop. HelloWorldAspect定義通知實現:
package com.nieshenkuan.aop;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 定義切面支持類 有了目標類,該定義切面了,切面就是通知和切入點的組合,而切面是通過配置方式定義的,
* 因此這定義切面前,我們需要定義切面支持類,切面支持類提供了通知實現
*
* @author NSK
*
*/
public class HelloWorldAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("===========before advice");
}
// 后置最終通知
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
// 新添加的方法=====================================
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=================================================
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
}
// 新添加的方法,后置通知=================================================
// 新添加的方法,后置異常通知=============================
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
// 新添加的方法,后置異常通知=============================
// 新添加的方法,環繞通知=============================
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("===========around before advice");
Object retVal = pjp.proceed(new Object[] { "replace" });
System.out.println("===========around after advice");
return retVal;
}
// 新添加的方法,環繞通知=============================
}
最后在applicationContext4.xml配置文件中接著前置通知配置的例子添加如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<aop:config>
<aop:aspect ref="aspect">
<!-- 前置通知 -->
<aop:before
pointcut="execution(* com.nieshenkuan..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)" arg-names="param" />
<!-- 后置異常通知================================= -->
<aop:after-throwing
pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
<!-- 后置異常通知================================= -->
<!-- 后置通知============================== -->
<aop:after-returning
pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
<!-- 后置通知============================== -->
<!-- 后置最終通知========================== -->
<aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
method="afterFinallyAdvice" />
<!-- 后置最終通知 ======================== -->
<!-- 環繞通知 ======================== -->
<aop:around pointcut="execution(* com.nieshenkuan..*.sayAround(..))"
method="aroundAdvice"/>
<!-- 環繞通知 ======================== -->
</aop:aspect>
</aop:config>
</beans>
測試代碼Test.java
/**
* 環繞通知
*/
@org.junit.Test
public void testSchemaAroundAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAround("haha");
System.out.println("======================================");
}
輸出:
======================================
===========around before advice
============around param:replace
===========around after advice
======================================
分析一下吧:
1)切入點匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAround(..))”匹配目標方法sayAround;
2)目標方法定義:使用method="aroundAdvice"指定環繞通知實現方法,在該實現中,第一個方法參數為pjp,類型為ProceedingJoinPoint,其中“Object retVal = pjp.proceed(new Object[] {"replace"});”,用于執行目標方法,且目標方法參數被“new Object[] {"replace"}”替換,最后返回“retVal ”返回值。
3)測試:我們使用“helloworldService.sayAround("haha");”傳入參數為“haha”,但最終輸出為“replace”,說明參數被替換了。
3.4 、引入
Spring引入允許為目標對象引入新的接口,通過在< aop:aspect>標簽內使用< aop:declare-parents>標簽進行引入,定義方式如下:
<aop:declare-parents
types-matching="AspectJ語法類型表達式"
implement-interface=引入的接口"
default-impl="引入接口的默認實現"
delegate-ref="引入接口的默認實現Bean引用"/>
- types-matching:匹配需要引入接口的目標對象的AspectJ語法類型表達式;
- implement-interface:定義需要引入的接口;
-
default-impl和delegate-ref:定義引入接口的默認實現,二者選一,default-impl是接口的默認實現類全限定名,而delegate-ref是默認的實現的委托Bean名;
接下來讓我們練習一下吧:
首先定義引入的接口及默認實現:
package com.nieshenkuan.aop;
/**
* 被引入的接口
* @author NSK
*
*/
public interface IIntroductionService {
void induct();
}
package com.nieshenkuan.aop;
/**
* 被引入接口的實現
*
* @author NSK
*
*/
public class IntroductiondServiceImpl implements IIntroductionService {
@Override
public void induct() {
System.out.println("=========introduction");
}
}
其次在applicationContext5.xml配置文件中接著前置通知配置的例子添加如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<!-- 使用CGLIB代理創建代理對象,默認使用JDK代理 -->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
<aop:config>
<aop:aspect ref="aspect">
<!-- 前置通知 -->
<aop:before
pointcut="execution(* com.nieshenkuan..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)" arg-names="param" />
<aop:declare-parents types-matching="com.nieshenkuan..*.IHelloWorldService+"
implement-interface="com.nieshenkuan.aop.IIntroductionService"
default-impl="com.nieshenkuan.aop.IntroductiondServiceImpl" />
<!-- 后置異常通知================================= -->
<aop:after-throwing
pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
<!-- 后置異常通知================================= -->
<!-- 后置通知============================== -->
<aop:after-returning
pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
<!-- 后置通知============================== -->
<!-- 后置最終通知========================== -->
<aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
method="afterFinallyAdvice" />
<!-- 后置最終通知 ======================== -->
<!-- 環繞通知 ======================== -->
<aop:around pointcut="execution(* com.nieshenkuan..*.sayAround(..))"
method="aroundAdvice" />
<!-- 環繞通知 ======================== -->
</aop:aspect>
</aop:config>
</beans>
這里要注意一下,把代理改一下:不然得不到結果。。。。
<!-- 使用CGLIB代理創建代理對象,默認使用JDK代理 -->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
測試代碼:Test.java
/**
* 引入測試
*/
@org.junit.Test
public void testSchemaIntroduction() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext5.xml");
IIntroductionService introductionService = ctx.getBean("helloWorldService", IIntroductionService.class);
introductionService.induct();
System.out.println("======================================");
}
結果:
======================================
=========introduction
======================================
分析一下吧:
1)目標對象類型匹配:使用types-matching="com.nieshenkuan..*.IHelloWorldService+"匹配IHelloWorldService接口的子類型,如HelloWorldService實現;
2)引入接口定義:通過implement-interface屬性表示引入的接口,如“com.nieshenkuan.aop.IIntroductionService”。
3)引入接口的實現:通過default-impl屬性指定,如“com.nieshenkuan.aop.IntroductiondServiceImpl”,也可以使用“delegate-ref”來指定實現的Bean。
4)獲取引入接口:如使用“ctx.getBean("helloWorldService", IIntroductionService.class);”可直接獲取到引入的接口。
3.5、 Advisor
Advisor表示只有一個通知和一個切入點的切面,由于Spring AOP都是基于AOP聯盟的攔截器模型的環繞通知的,所以引入Advisor來支持各種通知類型(如前置通知等5種),Advisor概念來自于Spring1.2對AOP的支持,在AspectJ中沒有相應的概念對應。
Advisor可以使用<aop:config>標簽下的<aop:advisor>標簽定義:
<aop:advisor pointcut="切入點表達式" pointcut-ref="切入點Bean引用"
advice-ref="通知API實現引用"/>
- pointcut和pointcut-ref :二者選一,指定切入點表達式;
- advice-ref:引用通知API實現Bean,如前置通知接口為MethodBeforeAdvice;
接下來讓我們看一下示例吧:
首先在com.nieshenkuan.aop.IHelloWorldService定義一個測試方法:
package com.nieshenkuan.aop;
/**
* 定義目標接口:創建被代理的接口
*
* @author NSK
*
*/
public interface IHelloWorldService {
void sayHello();
// 新添加的方法,前置通知=================================================
void sayBefore(String param);
// 新添加的方法,前置通知=================================================
// 新添加的方法,后置通知=============================
boolean sayAfterReturning();
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
void sayAfterThrowing();
// 新添加的方法,后置異常通知=============================
// 新添加的方法,后置最終通知=============================
boolean sayAfterFinally();
// 新添加的方法,后置最終通知=============================
// 新添加的方法,環繞通知=============================
void sayAround(String param);
// 新添加的方法,環繞通知=============================
// advisor測試
void sayAdvisorBefore(String param);
// advisor測試
}
其次在com.nieshenkuan.aop. HelloWorldService定義實現:
package com.nieshenkuan.aop;
/**
* 定義目標接口實現:接口的實現類
*
* @author NSK
*
*/
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
// 新添加的方法=================================================
@Override
public void sayBefore(String param) {
System.out.println("============say " + param);
}
// 新添加的方法=================================================
// 新添加的方法,后置通知=============================
@Override
public boolean sayAfterReturning() {
System.out.println("============after returning");
return true;
}
// 新添加的方法,后置通知=============================
// 新添加的方法,后置異常通知=============================
@Override
public void sayAfterThrowing() {
System.out.println("============before throwing");
throw new RuntimeException();
}
// 新添加的方法,后置異常通知=============================
// 新添加的方法,后置最終通知=============================
@Override
public boolean sayAfterFinally() {
System.out.println("============before finally");
throw new RuntimeException();
}
// 新添加的方法,后置最終通知=============================
// 新添加的方法,環繞通知=============================
@Override
public void sayAround(String param) {
System.out.println("============around param:" + param);
}
// 新添加的方法,環繞通知=============================
// advisor測試
@Override
public void sayAdvisorBefore(String param) {
System.out.println("============say " + param);
}
// advisor測試
}
第三定義前置通知API實現:
package com.nieshenkuan.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeAdviceImpl implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("===========before advice");
}
}
在applicationContext6.xml配置文件中先添加通知實現Bean定義:
<!-- 定義bean -->
<bean id="beforeAdvice" class="com.nieshenkuan.aop.BeforeAdviceImpl" />
然后在<aop:config>標簽下,添加Advisor定義,添加時注意順序:
<aop:advisor pointcut="execution(* com.nieshenkuan..*.sayAdvisorBefore(..))"
advice-ref="beforeAdvice" />
整個applicationContext6.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>
<!-- 定義bean -->
<bean id="beforeAdvice" class="com.nieshenkuan.aop.BeforeAdviceImpl" />
<bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
<!-- 使用CGLIB代理創建代理對象,默認使用JDK代理 -->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
<aop:config>
<aop:advisor pointcut="execution(* com.nieshenkuan..*.sayAdvisorBefore(..))"
advice-ref="beforeAdvice" />
<aop:aspect ref="aspect">
<!-- 前置通知 -->
<aop:before
pointcut="execution(* com.nieshenkuan..*.sayBefore(..)) and args(param)"
method="beforeAdvice(java.lang.String)" arg-names="param" />
<!-- 引入接口================================== -->
<aop:declare-parents types-matching="com.nieshenkuan..*.IHelloWorldService+"
implement-interface="com.nieshenkuan.aop.IIntroductionService"
default-impl="com.nieshenkuan.aop.IntroductiondServiceImpl" />
<!-- 引入接口================================== -->
<!-- 后置異常通知================================= -->
<aop:after-throwing
pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
<!-- 后置異常通知================================= -->
<!-- 后置通知============================== -->
<aop:after-returning
pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
<!-- 后置通知============================== -->
<!-- 后置最終通知========================== -->
<aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
method="afterFinallyAdvice" />
<!-- 后置最終通知 ======================== -->
<!-- 環繞通知 ======================== -->
<aop:around pointcut="execution(* com.nieshenkuan..*.sayAround(..))"
method="aroundAdvice" />
<!-- 環繞通知 ======================== -->
</aop:aspect>
</aop:config>
</beans>
測試代碼:Test.java
@org.junit.Test
public void testSchemaAdvisor() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext6.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayAdvisorBefore("haha");
System.out.println("======================================");
}
結果:
======================================
===========before advice
============say haha
======================================
在此我們只介紹了前置通知API,其他類型的在后邊章節介紹。
不推薦使用Advisor,除了在進行事務控制的情況下,其他情況一般不推薦使用該方式,該方式屬于侵入式設計,必須實現通知API。
未完.........