一、在軟件系統中,有不少功能是被動調用的(這些功能不是主要關注的),在不少地方都須要明確的調用。這種被分散到多處的功能稱爲「橫切關注點」。將橫切關注點和業務邏輯分析是AOP要解決的問題。AOP將橫切關注點模塊化爲了一些特殊的類,這些類稱爲切面。(以聲明的方法定義橫切關注點要以什麼時候何地調用)
二、AOP術語:
1:通知(advice)
切面必需要完成的工做稱之爲通知。
通知除了描述要完成的工做以外,還描述了什麼時候執行這個工做,它應該應用在什麼方法以前,什麼方法以後(方法鏈接點)。
5種類型的通知:
1:前置通知:在目標方法調用以前調用通知功能
2:後置通知:在目標方法完成以後調用通知,此時不會關心方法的輸出是什麼
3:返回通知:在目錄方法成功執行以後調用的通知
4:異常通知:在目標方法拋出異常時調用的通知
5:環繞通知:包含了被通知的方法,在被通知方法執行以前和執行以後執行的自定義行爲。
2:鏈接點(Join point)
應用執行過程當中可以插入切面的一個點,能夠是一個方法調用時、拋出異常時、修改字段時(其實就是一個觸發的時機,查電錶的電錶)。
3:切點(pointcut)
切點匹配一部分鏈接點。
4:切面(Aspect)
切面是切點和通知的結合(切面知道本身要對誰作什麼事情),通知和切點共同定義了切面的所有內容(是什麼,在什麼時候何處完成其功能)
5:引入(Introduction):
引入容許咱們向現有的類添加新方法和新屬性。
6:織入(Weaving):
把切面應用到目標對象,並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象中。在目標對象的生命週期中的編譯器、類加載期、運行期被織入:
三、Spring AOP 使用代理類將目標對象包裹。當調用者調用目標對象時,會被代理類攔截。
因爲Spring AOP 是基於代理的,因此只支持方法鏈接點(不支持構造方法鏈接點)。
四、Spring AOP僅支持AspectJ切點指示器的一個子集。
arg() 限制鏈接點匹配參數爲指定類型的執行方法
@args() 限制鏈接點匹配參數由指定註解標註的執行方法
execution() 用於匹配是鏈接點的執行方法
this() 限制鏈接點匹配 AOP 代理的 bean 引用爲指定類型的類
target 限制鏈接點匹配目標對象爲指定類型的類
@target() 限制鏈接點匹配特定的執行對象,這些對象對應的類要具備指定類型的註解
within() 限制鏈接點匹配指定的類型
@within() 限制鏈接點匹配指定註解所標註的類型(當使用 Spring AOP 時,方法定義在由指定的註解所標註的類裏)
@annotation 限定匹配帶有指定註解的鏈接點
五、編寫切點
例如:
//定義一個表示表演的接口
package concert;
public interface performance{
public void perform();
}
//咱們要編寫一個perform()觸發的通知。下面是一個切點表達式,這個切點表達式設置了perform調用觸發通知的調用。
execution(* concert.Performance.perform(..))
說明:
execution:在方法執行時觸發
*:不關心返回值
concert.Performance.perform:方法全路徑
(..)任意參數
如今咱們配置的切點只匹配concert包。可使用within()指示器來限制匹配。
execution(* concert.Performance.perform(..)) && within(concert.*)
說明:
&& : 且(還有|| or !)
within(concert.*) : concert包下的任意方法被調用。
六、在切點中選擇bean
bean() 是Spring新引入的指示器,能夠在切點表達式中使用bean的ID來標識bean。
使用beanID或者名稱做爲參數來限制切點只匹配到特定的bean。
例如:
execution(* concert.Performance.perform()) and bean('BeanID')
執行concert.Performance.perform()時觸發,可是bean的ID是'BeanID'
相似的:
execution(* concert.Performance.perform()) and !bean('BeanID')
七、使用註解建立切面:
AspectJ提供了5個註解來代表應該何時調用:
@After 通知方法會在目標方法返回或拋出異常後調用
@AfterReturning 通知方法會在目標方法返回後調用
@AfterThrowing 通知方法會在目標方法拋出異常後調用
@Around 通知方法會將目標方法封裝起來
@Before 通知方法會在目標方法調用以前執行
下面使用@Aspect註解定義了一個「觀衆」切面(咱們認爲:觀衆是表演的橫向關注點)
package concert;
@Aspect //定義了一個切面
public class Audience{
@Before("execution("** concert.Performance.perform(..)")") //這個通知方法會在目標方法調用以前調用
public void 手機靜音(){//演出以前鼓掌}
@AfterReturning("execution("** concert.Performance.perform(..)")") //這個通知方法會在目標方法調用以後調用
public void 鼓掌(){//表演以後鼓掌}
@AfterThrowing("execution("** concert.Performance.perform(..)")") //這個通知方法會在目標方法拋出異常時調用
public void 退款(){//表演失敗時要求退款}
}//上面這段代碼重複的傳入了3次切點表達式。
可使用@Pointcut註解定義重用的節點。
package concert;
@Aspect //定義了一個切面
public class Audience{
@Pointcut("execution("** concert.Performance.perform(..)")") //定義命名的切點
public void performance(){} //這個方法的方法體不重要,這個方法只是@Pointcut的載體
@Before("performance()")
public void 手機靜音(){}
@AfterReturning("performance()")
public void 鼓掌(){}
@AfterThrowing("performance()")
public void 退款(){}
}
八、啓動AspectJ註解的自動代理
(7中配置了切面,只有啓用了註解的自動代理才能夠用)
1:在JavaConfig中配置:
@Configuration
@EnableAspectJAutoproxy //啓動AspectJ自動代理
@ComponentScan
public class Config{
@Bean
public Audience audience(){return new Audience();} //聲明Audience的Bean
}
2:使用xml配置:
<beans xmlns:xxx/yyy/aop> //引入Spring AOP命名空間
<context:component-scan base-package = "concert">
<aop:aspectJ-autoproxy /> //啓用AspectJ自動代理
<bean class = "concert.Audience">
</beans>
注意:不管使用哪一種方法,@Aspect註解的bean都會被建立一個代理,包裹切點匹配到的全部的bean周圍。(造成了代理包裹目標bean的模型)
九、建立環繞通知
在一個通知方法中同時編寫前置和後置通知。
例如:
@Aspect
public class Audience{
@Pointcut("execution(** concert.Performance.perform(..))") //定義命名的切點
public void performance(){}
@Around("performance()") //環繞通知方法(表示watchPerformance方法將做爲performance切點的環繞通知)
public void watchPerformance(ProceedingJoinPoint jp){
try{
//調用前置通知
jp.proceed(); //掉用ProceedingJoinPoint的proceed()將控制權交給目標方法。(若是不寫,將致使目標方法的調用阻塞。屢次調用能夠實現重試)
//調用後置通知
}catch(Throwable e){
//調用異常通知
}
}
}
十、處理通知中的參數:
以前說過光盤中的磁道。假設:playTrack()方法用於播放光盤的某一個磁道,若是想要記錄磁道的播放次數。最簡單的辦法是在playTrack()中添加一個計數的標記。可是計數這個事情怎麼看都應該又切面來完成。
使用切面記錄磁道播放次數的例子:
@Aspect
public class TrackCount{
private Map<Integer , Integer> trackCounts = new HashMap<Integer , Integer>();
//playTrack(int):接收int類型的參數 args(trackNumber):指定參數。這個參數最終會被傳遞到通知中去。
@Pointcut("execution(* xxx.yyy.playTrack(int)) && args(trackNumber)")
public void trackPlayed(int trackNumber){}
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber){
int currentCount = trackCounts.containsKey(trackNumber) ? trackCount.get(trackNumber) : 0;
trackCount.put(trackNumber , currentCount + 1);
}
}
//接下來將光盤類和TrackCount類定義爲Bean,並啓用AspectJ自動代理:
@Configuration
@EnableAspectJAutoproxy //啓動AspectJ自動代理
public class TrackCounterConfig{
@Bean
public 光盤類 方法名(){
光盤 盤 = new 光盤();
。。。
return 盤;
}
@Bean
public TrackCounter trackCounter(){
return new TrackCounter();
}
}
十一、經過註解引入新功能
?????????????????????????????
十二、在xml中聲明切面
Spring的aop命名空間中,提供了多個元素在xml中聲明切面:
<aop:advisor> 定義 AOP 通知器
<aop:after> 定義 AOP 後置通知(無論被通知的方法是否執行成功)
<aop:after-returning> 定義 AOP 返回通知
<aop:after-throwing> 定義 AOP 異常通知
<aop:around> 定義 AOP 環繞通知
<aop:aspect> 定義一個切面
<aop:aspectj-autoproxy> 啓用@AspectJ註解驅動的切面
<aop:before> 定義一個 AOP 前置通知
<aop:config> 頂層的 AOP 配置元素。大多數的<aop:*>元素必須包含在<aop:config>元素內
<aop:declare-parents> 以透明的方式爲被通知的對象引入額外的接口
<aop:pointcut> 定義一個切點
例如:
<aop:config>
<aop:aspect ref = "pojoBean"> //這個POJOBean中定義了切面的功能(就是前置通知方法,後置通知方法等)
<aop:before pointcut = "execution(** xxx.yyy.perform(..))" method = "method1" /> //method1.2.3都定義在了上面引入的pojoBean
<aop:after-returning "pointcut = "execution(** xxx.yyy.perform(..))" method = "method2" />
<aop:after-throwing "pointcut = "execution(** xxx.yyy.perform(..))" method = "method3" />
</aop:aspect>
</aop:config>
//上面的切點重複編寫了。
抽取切點聲明:
<aop:config>
<aop:aspect ref = "pojoBean"> //這個POJOBean中定義了切面的功能(就是前置通知方法,後置通知方法等)
<aop:pointcut id = "performance" expression = "execution(** xxx.yyy.perform(..))" />
<aop:before pointcut-ref = "performance" method = "method1" /> //method1.2.3都定義在了上面引入的pojoBean
<aop:after-returning pointcut-ref = "performance" method = "method2" />
<aop:after-throwing pointcut-ref = "performance" method = "method3" />
</aop:aspect>
</aop:config>
1三、使用xml定義環繞通知
去掉註解的AOP環繞通知方法
public class Audience{
public void watchPerformance(ProceedingJoinPoint jp){
try{
前置通知();
jp.proceed();
後置通知();
}catch(Throwable e){
異常通知();
}
}
}
在xml中使用<aop:around>元素聲明環繞通知
<aop:config>
<aop:aspect ref = "audience"> //連接環繞方法所在的Bean
<aop:pointcut id = "performance" expression = "execution(** xxx.yyy.perform(..))"> //定義切點
<aop:around pointcut-ref = "performance" method = "watchPerformance"> //給切點關聯環繞通知方法。
</aop:config>
1四、使用xml爲通知傳遞參數:
上面的筆記中有提到。記錄光盤的磁道賭取次數的案例,下面是使用xml配置的相同案例:
將切面註解去掉後的代碼:
public class TrackCount{
private Map<Integer , Integer> trackCounts = new HashMap<Integer , Integer>();
public void countTrack(int trackNumber){
int currentCount = trackCounts.containsKey(trackNumber) ? trackCount.get(trackNumber) : 0;
trackCount.put(trackNumber , currentCount + 1);
}
}
xml配置文件;
<beans>
<bean id = "trackCount" class = "xxx.yyy.TrackCount">
<bean id = "cd" class = "xxx.yyy.光盤">
<property name = "" value = "">
<property name = "磁道">
<list>
<value>"..."</value>
</list>
</property>
</bean>
<aop:config>
<aop:aspect ref = "trackCount"> //將TrackCount聲明爲切面
<aop:pointcut id = "trackPlayed" expression = "execution(* xxx.yyy.playTrack(int)) and args(trackNumber)" />
<aop:before pointcut-ref = "trackPlayed" method = "countTrack" />
</aop:aspect>
</aop:config>
</beans>
1五、經過xml引入新的功能
????????????
1六、注入AspectJ切面
Spring的AOP解決方法相對於AspectJ很是粗糙的。可使用Spring的依賴注入將AspectJ切面注入到Spring中使用。
???????????