面向切面編程(Aspect-oriented Programming,AOP)經過提供另外一種思考程序結構的方法來補充面向對象編程(Object-oriented Programming,OOP)。OOP中模塊化的關鍵單元是類,而AOP中模塊化的單元是切面。切面支持跨多個類型和對象的關注點(例如事務管理)的模塊化java
在使用 AOP 以前,先熟悉一下 AOP 概念和術語。這些術語並不特定於 Spring,而是與 AOP 有關的spring
項 | 描述 |
---|---|
Aspect(切面) | 跨越多個類的關注點的模塊化,切面是通知和切點的結合。通知和切點共同定義了切面的所有內容——它是什麼,在什麼時候和何處完成其功能。事務處理和日誌處理能夠理解爲切面 |
Join point(鏈接點) | 程序執行過程當中的一個點,如方法的執行或異常的處理 |
Advice(通知) | 切面在特定鏈接點上採起的動做 |
Pointcut(切點) | 匹配鏈接點的斷言。通知與切入點表達式相關聯,並在切入點匹配的任何鏈接點上運行(例如,具備特定名稱的方法的執行)。切入點表達式匹配的鏈接點概念是AOP的核心,Spring默認使用AspectJ切入點表達式語言 |
Introduction(引用) | 爲類型聲明其餘方法或字段。Spring AOP容許您向任何建議的對象引入新的接口(和相應的實現)。例如,您可使用介紹使bean實現IsModified接口,以簡化緩存 |
Target object(目標) | 由一個或多個切面通知的對象。也稱爲「通知對象」。因爲Spring AOP是經過使用運行時代理實現的,因此這個對象始終是代理對象 |
AOP proxy(代理) | AOP框架爲實現切面契約(通知方法執行等)而建立的對象。在Spring框架中,AOP代理是JDK動態代理或CGLIB代理 |
Weaving(織入) | 織入是將通知添加對目標類具體鏈接點上的過程,能夠在編譯時(例如使用AspectJ編譯器)、加載時或運行時完成 |
Spring切面能夠應用5種類型的通知:express
其中環繞通知包括前置、後置、返回、異常通知,這個在後面的例子體現編程
xml配置中須要aop命名空間標記,須要先導入 spring-aop 模式緩存
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> </beans>
也是須要AspectJ的 aspectjweaver.jar
包,spring-aspects
模塊是依賴該jar包的
spring-aspects
模塊集成自 AspectJ 框架, 主要是爲 Spring AOP 提供多種 AOP 實現方法app
使用 <aop:aspect>
標籤聲明切面,使用 ref 屬性去引用支持的 bean框架
<aop:config> <aop:aspect id="myAspect" ref="aspectBean"> ... </aop:aspect> </aop:config> <bean id="aspectBean" class="com.demo.aspect.AspectBean"> ... </bean>
切面能夠具備與任何其餘類相同的方法和字段。它們還能夠包含切入點、通知和引入(內部類型)聲明。模塊化
聲明切入點是肯定通知感興趣的或是將要織入的鏈接點(即目標方法)測試
<aop:config> <aop:aspect id="myAspect" ref="aspectBean"> <aop:pointcut id="businessService" expression="execution(* com.demo.service.*.*(..))"/> ... </aop:aspect> </aop:config> <bean id="aspectBean" class="com.demo.aspect.AspectBean"> </bean>
表達式 expression 是對鏈接點的篩選,上面的例子是 com.demo.service 包下全部類的全部方法
詳細的表達式語法見後面詳說 也還能夠經過名稱匹配切入點參數與建議方法參數ui
使用 <aop:{ADVICE-NAME}>
標籤聲明五個建議中的任意一個,以下
<aop:config> <aop:aspect id="myAspect" ref="aspectBean"> <aop:pointcut id="businessService" expression="execution(* com.demo.service.*.*(..))" /> <!-- 前置通知定義 --> <aop:before pointcut-ref="businessService" method="doBeforeTask" /> <!-- 後置通知定義 --> <aop:after pointcut-ref="businessService" method="doAfterTask" /> <!-- 返回通知定義 --> <!-- doReturnTask方法必需要有一個名字與「returning」值一致(如retVal)的參數,這個是目標方法的返回值 --> <aop:after-returning pointcut-ref="businessService" returning="retVal" method="doReturnTask" /> <!-- 異常通知定義 --> <!-- doRequiredTask方法必需要有一個名字與「throwing」值一致(如ex)的參數,這個是目標方法的拋出的異常--> <aop:after-throwing pointcut-ref="businessService" throwing="ex" method="doThrowTask" /> <!-- 環繞通知定義(環繞通知包含了其餘的通知) --> <!-- <aop:around pointcut-ref="businessService" method="doAroundTask" /> --> </aop:aspect> </aop:config> <bean id="aspectBean" class="com.demo.aspect.AspectBean"> </bean>
其中環繞通知包括前置、後置、返回、異常通知,在待會的例子中能夠看出
這裏pointcut-ref="businessService"是在<aop:config>
裏定義的切入點。也能夠改成內聯切入點,使用pointcut屬性替換pointcut-ref屬性
<aop:aspect id="beforeExample" ref="aBean"> <aop:before pointcut="execution(* com.demo.service.*.*(..))" method="doAccessCheck"/> ... </aop:aspect>
例子
咱們使用上面的例子,定義的切入點爲 com.demo.service 下的全部的類的全部方法,該切面支持的bean爲 com.demo.aspect 下的 AspectBean。咱們的切入點也是須要在IoC容器中被管理的 bean,能夠用註解也能夠xml配置
<bean id="targetService" class="com.demo.service.TargetService"/>
TargetService,目標對象,這個operation方法即是咱們的切入點
public class TargetService { public String operation(String msg) { System.out.println("執行目標方法,方法參數[msg:" + msg + "]"); // 測試異常通知 // throw new RuntimeException("我是個異常"); return msg; } }
同時我也寫一下 AspectBean 裏面的方法
public class AspectBean { private static int step = 0; public void doBeforeTask(String msg) { System.out.println(++step + " 前置通知,參數爲[msg:" + msg + "]"); } public void doAfterTask() { System.out.println(++step + " 後置通知"); } public void doReturnTask(Object retVal) { System.out.println(++step + " 返回通知,返回值爲:" + retVal.toString()); } public void doThrowTask(Exception ex) { System.out.println(++step + " 異常通知,異常信息爲:" + ex.getMessage()); } /** * 環繞通知須要攜帶ProceedingJoinPoint類型的參數 * 環繞通知相似於動態代理的全過程ProceedingJoinPoint類型的參數能夠決定是否執行目標方法 * 且環繞通知必須有返回值,返回值即目標方法的返回值 */ public Object doAroundTask(ProceedingJoinPoint pjp) { String methodname = pjp.getSignature().getName(); Object result = null; try { // 前置通知 System.out.println("目標方法" + methodname + "開始,參數爲" + Arrays.asList(pjp.getArgs())); // 執行目標方法 result = pjp.proceed(); // 返回通知 System.out.println("目標方法" + methodname + "執行成功,返回" + result); } catch (Throwable e) { // 異常通知 System.out.println("目標方法" + methodname + "拋出異常: " + e.getMessage()); } // 後置通知 System.out.println("目標方法" + methodname + "結束"); return result; } }
咱們先註釋掉xml中環繞通知的配置,運行一下看下結果如何
public class MainApp { public static void main( String[] args ){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); TargetService targetService = (TargetService) applicationContext.getBean("targetService"); targetService.operation("zou"); } }
結果爲
1 前置通知 執行目標方法,方法參數[msg:zou] 2 後置通知 3 返回通知,返回值爲:zou
而後咱們註釋掉其餘其餘通知,測試一下環繞通知,而且咱們在目標方法中拋出一個異常,再運行一下,結果爲
目標方法operation開始,參數爲[zou] 執行目標方法,方法參數[msg:zou] 目標方法operation拋出異常: 我是個異常 目標方法operation結束
要在Spring配置中使用@AspectJ切面,須要啓用Spring支持
使用XML配置啓用@AspectJ支持
<aop:aspectj-autoproxy/>
這裏也須要AspectJ的 aspectjweaver.jar
包,spring-aspects
模塊是依賴該jar包的
Aspects 類和其餘任何正常的 bean 同樣,除了它們將會用 @AspectJ 註釋以外,它和其餘類同樣可能有方法和字段
import org.aspectj.lang.annotation.Aspect; @Aspect public class AnnotationAspect { }
也能夠在 xml 中配置,和其餘 bean 同樣
<bean id="myAspect" class="org.xyz.AnnotationAspect"> <!-- configure properties of the aspect here --> </bean>
切面(用@Aspect標註的類)能夠具備與任何其餘類相同的方法和字段。它們還能夠包含切入點、通知和引入(內部類型)聲明。
切入點聲明有兩部分:包含名稱和任何參數的簽名,以及準確肯定咱們感興趣的方法執行的切入點表達式
在AOP的@AspectJ註釋樣式中,切入點簽名是由一個常規方法定義提供的,切入點表達式是經過使用@Pointcut註釋表示的(做爲切入點簽名的方法必須爲void返回類型)
以下代碼,定義了一個名爲anyOldTransfer
的切入點,該切入點與任何名爲transfer
的方法的執行匹配
@Pointcut("execution(* transfer(..))")// the pointcut expression private void anyOldTransfer() {}// the pointcut signature
表達式見
你可使用 @{ADVICE-NAME} 註釋聲明五個建議中的任意一個,以下所示。這假設你已經定義了一個切入點標籤方法 operation()
@Aspect public class AnnotationAspect { private static int step = 0; @Pointcut("execution(* transfer(..))") // the pointcut expression private void operation() {} @Before("operation()") public void doBeforeTask() { System.out.println(++step + " 前置通知"); } @After("operation()") public void doAfterTask() { System.out.println(++step + " 後置通知"); } @AfterReturning(pointcut = "operation()", returning = "retVal") public void doAfterReturnningTask(Object retVal) { System.out.println(++step + " 返回通知,返回值爲:" + retVal.toString()); } @AfterThrowing(pointcut = "operation()", throwing = "ex") public void doAfterThrowingTask(Exception ex) { System.out.println(++step + " 異常通知,異常信息爲:" + ex.getMessage()); } /** * 環繞通知須要攜帶ProceedingJoinPoint類型的參數 * 環繞通知相似於動態代理的全過程ProceedingJoinPoint類型的參數能夠決定是否執行目標方法 * 且環繞通知必須有返回值,返回值即目標方法的返回值 */ //@Around("operation()") public Object doAroundTask(ProceedingJoinPoint pjp) { String methodname = pjp.getSignature().getName(); Object result = null; try { // 前置通知 System.out.println("目標方法" + methodname + "開始,參數爲" + Arrays.asList(pjp.getArgs())); // 執行目標方法 result = pjp.proceed(); // 返回通知 System.out.println("目標方法" + methodname + "執行成功,返回" + result); } catch (Throwable e) { // 異常通知 System.out.println("目標方法" + methodname + "拋出異常: " + e.getMessage()); } // 後置通知 System.out.println("目標方法" + methodname + "結束"); return result; } }
也能夠爲任意一個通知直接寫入內聯切入點
@Before("execution(* com.xyz.myapp.dao.*.*(..))") public void doAccessCheck() { // ... }
其中環繞通知包括前置、後置、返回、異常通知 運行的結果這裏不給了,能夠參考上面xml配置的例子