版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/SunnyYoona/article/details/50655462
切點用于準确定位應該在什麼地方應用切面的通知。切點和通知是切面的最基本元素。
在Spring AOP中,需要使用AspectJ的切點表達式語言來定義切點。關于Spring AOP的AspectJ切點,最重要的一點是Spring僅支援AspectJ切點訓示器的一個子集。
類型 | 說明 |
---|---|
arg() | 限制連接配接點比對參數為指定類型的執行方法。 |
@args() | 限制連接配接點比對參數由指定注解标注的執行方法。 |
execution() | 用于比對的是連接配接點的執行方法。 |
this() | 限制連接配接點比對AOP代理的Bean引用為指定類型的類。 |
target() | 限制連接配接點比對目标對象為指定類型的類。 |
@target() | 限制連接配接點比對特定的執行對象,這些對象對應的類具備指定類型的注解。 |
within() | 限制連接配接點比對指定的類型。 |
@within() | 限制連接配接點比對指定注解所标注的類型(當使用Spring AOP時,方法定義再由指定的注解所标注的類裡)。 |
@annotation | 限制比對帶有指定注解連接配接點。 |
在Spring中嘗試使用AspectJ其他訓示器時,将會抛出IllegalArgumentException異常。
注意:
隻有execution訓示器是唯一的執行比對,而其他的訓示器都是用于限制比對的。這說明execution訓示器是我們在編寫切點定義時最主要的訓示器。在此基礎上,我們使用其他訓示器來限制所比對的切點。 |
1. 編寫切點
如下圖所示的切點表達式表示當Singer的perform()方法執行時會觸發通知。我們使用execution()訓示器選擇Singer的perform()方法。方法表達式以*号開始,标示了我們不關心傳回值的類型。然後,我們指定了全限定類名和方法名。對于方法參數清單,我們使用(..)标示切點選擇任意的perform()方法,無論該方法的參數是什麼。
除此之外,我們還可以對上面的比對進行限制,可以使用within()訓示器來限制比對。
我們使用&&操作符把execution()和within()訓示器連接配接在一起形成and關系(切點必須比對所有的訓示器)。
2. 在XML中聲明切面
Spring的AOP配置元素簡化了基于POJO切面的聲明:
<aop:advisor> | 定義AOP通知器。 |
<aop:after> | 定義AOP後置通知(不管被通知的方法是否執行成功) |
<aop:after-returning> | 定義AOP after-returning通知。 |
<aop:after-throwing> | 定義 AOP after-throwing 通知。 |
<aop:around> | 定義 AOP 環繞通知。 |
<aop:aspect> | 定義切面。 |
<aop:aspectj-autoproxy> | 啟用@AspectJ注解驅動的切面。 |
<aop:before> | 定義 AOP前置通知。 |
<aop:config> | 頂層的AOP配置元素。大多數的<aop:*>元素必須包含在<aop:config>元素内。 |
<aop:declare-parents> | 為被通知的對象引入額外的接口,并透明的實作。 |
<aop:pointcut> | 定義切點。 |
為了闡述Spring AOP,我們建立一個觀衆類(Audience)類:
package com.sjf.bean;
/**
* 觀衆類
* @author sjf0115
*
*/
public class Audience {
public void takeSeats(){
System.out.println("the audience is taking their seats...");
}
public void applaud(){
System.out.println("very good, clap clap clap...");
}
public void demandRefund(){
System.out.println("very bad, We want our money back...");
}
}
Audience類并沒有任何特别之處,她就是一個有幾個方法的簡單Java類。我們可以像其他類一樣,利用XML把它注冊為Spring應用上下文中的一個Bean:
<bean id = "audience" class = "com.sjf.bean.Audience">
</bean>
我們需要Spring AOP就能把它成為一個切面。
2.1 前置聲明和後置聲明
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id = "singer" class = "com.sjf.bean.Singer">
</bean>
<bean id = "audience" class = "com.sjf.bean.Audience">
</bean>
<aop:config proxy-target-class="true">
<!-- 聲明定義一個切面 -->
<aop:aspect ref = "audience">
<!-- 表演之前 -->
<aop:before method="takeSeats" pointcut="execution(* com.sjf.bean.Singer.perform(..))"/>
<!-- 表演之後 -->
<aop:after-returning method="applaud" pointcut="execution(* com.sjf.bean.Singer.perform(..))"/>
<!-- 表演失敗之後 -->
<aop:after-throwing method="demandRefund" pointcut="execution(* com.sjf.bean.Singer.perform(..))"/>
</aop:aspect>
</aop:config>
</beans>
大多數的AOP配置元素必須在<aop:config>元素的上下文内使用。這條規則有幾種例外場景,但是把Bean聲明為一個切面時,我們總是從<aop:config>元素開始配置。
在<aop:config>元素内,我們可以聲明一個或者多個通知器,切面或者切點。上述例子中,我們使用<aop:aspect>元素聲明了一個簡單的切面。ref元素引用了一個Bean(Audience),該Bean實作了切面的功能。ref元素應用的Bean提供了在切面上通知所調用的方法。
該切面應用了3個不同的通知。<aop:before>元素定義了比對切點的方法執行之前調用前置通知方法,audience Bean 的takeSeats()方法。<aop:after-returning>元素定義了一個傳回後(after-returning)通知,在切點所比對的方法調用之後在執行applaud()方法。<aop:after-throwing>元素定義了抛出異常後通知,如果所有比對的方法執行時抛出任何異常,都将調用demandRefund()方法。
下面展示了通知邏輯如何嵌入到業務邏輯中:
在所有的通知元素中,pointcut屬性定義了通知所應用的切點。pointcut屬性的值是使用AspectJ切點表達式文法所定義的切點。
你或許注意到所有通知元素中的pointcut屬性的值都是一樣的,這是因為所有的通知都是應用到相同的切點上。這似乎違反了DRY(不要重複你自己)原則。我們做一下修改,可以使用<aop:pointcut>元素定義一個命名切點。
<aop:config proxy-target-class="true">
<!-- 聲明定義一個切面 -->
<aop:aspect ref = "audience">
<aop:pointcut id="singerPerfom" expression="execution(* com.sjf.bean.Singer.perform(..))" />
<!-- 表演之前 -->
<aop:before method="takeSeats" pointcut-ref="singerPerfom"/>
<!-- 表演之後 -->
<aop:after-returning method="applaud" pointcut-ref="singerPerfom"/>
<!-- 表演失敗之後 -->
<aop:after-throwing method="demandRefund" pointcut-ref="singerPerfom"/>
</aop:aspect>
</aop:config>
<aop:pointcut>元素定義了一個id為singerPerfom的切點,同時修改所有的通知元素,用pointcut-ref屬性來引用這個命名切點。
2.2 聲明環繞通知
如果不适用成員變量存儲資訊,那麼在前置通知和後置通知之間共享資訊是非常麻煩的。我們希望實作這樣一個功能:希望觀衆一直關注演出,并報告演出的演出時長。使用前置通知和後置通知實作該功能的唯一方式是:在前置通知中記錄開始時間,并在某個後置通知中報告演出的時長。但這樣的話,我們必須在一個成員變量中儲存開始時間。是以我們可以使用環繞通知,隻需在一個方法中實作即可。
public void PerformTime(ProceedingJoinPoint joinPoint){
// 演出之前
System.out.println("the audience is taking their seats...");
try {
long start = System.currentTimeMillis();
// 執行演出操作
joinPoint.proceed();
long end = System.currentTimeMillis();
// 演出成功
System.out.println("very good, clap clap clap...");
System.out.println("該演出共需要 "+(end - start) + " milliseconds");
} catch (Throwable e) {
// 演出失敗
System.out.println("very bad, We want our money back...");
e.printStackTrace();
}
}
對于這個新的通知方法,我們會注意到它使用了ProceedingJoinPoint作為方法的入參。這個對象非常重要,因為它能讓我們在通知裡調用被通知 的方法。如果希望把控制轉給被通知的方法時,我們可以調用ProceedingJoinPoint的proceed()方法。如果忘記調用proceed()方法,通知将會阻止被通知方法的調用。我們還可以在通知裡多次調用被通知方法,這樣做的一個目的是實作重試邏輯,在被通知的方法執行失敗時反複重試。
PerformTime()方法包含了之前3個通知方法的所有邏輯,并且該方法還會負責自身的異常處理。聲明環繞通知與聲明其他類型的通知并沒有太大的差別,隻需要<aop:around>元素。
<aop:config proxy-target-class="true">
<!-- 聲明定義一個切面 -->
<aop:aspect ref = "audience">
<aop:pointcut id="singerPerfom" expression="execution(* com.sjf.bean.Singer.perform(..))" />
<!-- 聲明環繞通知 -->
<aop:around method="performTime" pointcut-ref="singerPerfom"/>
</aop:aspect>
</aop:config>
運作結果:
the audience is taking their seats... 正在上演個人演唱會... very good, clap clap clap... 該演出共需要 37 milliseconds |
像其他通知的XML一樣,<aop:around>指定了一個切點和一個通知方法的名字。
2.3 為通知傳遞參數
到目前為止,我們的切面很簡單,沒有任何的參數。但是有時候通知并不是僅僅是對方法進行簡單包裝,還需要校驗傳遞給方法的參數值,這時候為通知傳遞參數就非常有用了。
下面是歌手的實體類:
package com.sjf.bean;
/**
* 歌手實體類
* @author sjf0115
*
*/
public class Singer {
public void perform(String songName) {
System.out.println("正在上演個人演唱會,演唱曲目為 " + songName);
}
}
在這我們提供了一個Organizers(主辦方)實體類,在歌手演唱之前截獲歌手演唱的曲目,然後通知給大家:
package com.sjf.bean;
/**
* 主辦方實體類
* @author sjf0115
*
*/
public class Organizers {
public void BeforeSong(String songName){
System.out.println("演唱會馬上就開始了,演唱歌曲為 " + songName);
}
public void AfterSong(){
System.out.println("演唱曲目結束,謝謝大家...");
}
}
我們像以前一樣使用<aop:aspect>,<aop:before>和<aop:after>元素。但是這次我們通過配置實作将被通知方法的參數傳遞給通知。
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id = "singer" class = "com.sjf.bean.Singer">
</bean>
<bean id = "Organizers" class = "com.sjf.bean.Organizers">
</bean>
<aop:config>
<!-- 聲明定義一個切面 -->
<aop:aspect ref = "Organizers">
<aop:pointcut id="singerPerform" expression="execution(* com.sjf.bean.Singer.perform(String)) and args(song)" />
<aop:pointcut id="singerPerform2" expression="execution(* com.sjf.bean.Singer.perform(..))" />
<aop:before method="BeforeSong" pointcut-ref="singerPerform" arg-names="song"/>
<aop:after-returning method="AfterSong" pointcut-ref="singerPerform2"/>
</aop:aspect>
</aop:config>
</beans>
關鍵之處在于切點定義和<aop:before>的arg-names屬性。切點标示了Singer的perform()方法,指定了String參數。然後在args參數中标示了song作為參數。同樣,<aop:before>元素引用了切點中song參數,标示該參數必須傳遞給Organizers的BeforeSong()方法。
演唱會馬上就開始了,演唱歌曲為 你是我的眼淚 正在上演個人演唱會,演唱曲目為 你是我的眼淚 演唱曲目結束,謝謝大家... |
來源于:《Spring實戰》