Spring基礎知識之基於註解的AOP
背景概念:
1)橫切關註點:散布在應用中多處的功能稱為橫切關註點
2)通知(Advice):切面完成的工作。通知定了了切面是什麽及何時調用。
5中可以應用的通知:
前置通知(Before):在目標方法被調用前調用通知功能。
後置通知(After):在目標方法完成後調用通知,此時不會關系方法輸出什麽。
返回通知(After-returning):在目標方法成功執行後調用通知。
異常通知(After-throwing):在目標方法拋出異常後調用通知。
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和之後執行自定義的方法。
3)連接點(Join Point):調用通知的時機稱為連接點。
4)切點(Pointcut):通知要織入的連接點的範圍。
5)切面(Aspect):切面:通知和切點的結合。
6)引入(Introduction):允許我們向現有的類添加新方法或屬性。
7)織入(weaving):把切面應用到目標對象並創建新的代理對象的過程。
切面在指定的連接點被織入到目標對象中,在目標對象的生命周期裏有多個點可以進行織入:
編譯期:切面在目標類編譯時織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
類加載期:切面在目標類被加載到JVM時織入。這種方式需要特殊的類加載器,他可以在目標類被引入應用之前增加該目標類的字節碼。AspectJ5的加載時織入,就支持以這種方式織入。
運行期:切面在應用運行的某個時刻被織入。一般情況下,在切面被織入時,AOP容器會為目標對象動態創建代理對象,SpringAOP就是以這種方式進行織入的。
註:註解的解釋:註解本身是沒有功能的,就和XML一樣,註解和XML都是一種元數據,元數據就是解釋數據的數據,這裏就是所謂的配置。
註解的功能來自用註解的這個地方
Spring對AOP的支持
SpringAOP存在的目的:解耦。
AOP可以讓一組類共享相同的行為。避免通過繼承等高耦合方式實現為類添加功能。
Spring支持4中類型的AOP支持:
1)基於代理的經典SpringAOP;
2)純POJO切面;
3)@AspectJ註解驅動的切面;
4)註入式AspectJ切面(適用於各個Spring版本)。
註:前三種都是SpringAOP實現的變體,SpringAOP構建在動態代理的基礎之上,因此,Spring對AOP的支持局限於方法攔截。
如果AOP需求超過了簡單的方法調用(如構造器或屬性攔截),那麽需要使用第四種方式。
Spring通知是java編寫的
Spring的通知是POJO實現的,可以基於註解和XML實現,相對簡單便捷。
AspectJ以java擴展的方式實現的,優點:特有的AOP語言可以獲得更強大的細粒度的控制以及更豐富的AOP工作集,但學習成本大。
Spring在運行時通知對象
Spring運行時才會創建代理對象,所以我們不需要特殊的編譯器來織入SpringAOP的切面。
Spring只支持方法級別的連接點
如果需要使用除方法攔截之外的連接點攔截功能,那麽我們可以利用Aspect來補充Spring AOP的功能。
通過切點來選擇連接點
註:只有execution指示器是實際執行匹配的,其他指示器都是限定匹配的,我們在編寫切點定義時最主要使用的指示器應當是:execution指示器,在此基礎上使用其他指示器來限制所匹配的切點。
編寫切點:
定義一個performance接口:
package com.spring.learn.index;
public interface Performance {
public void perform();
}
則我們想在表演接口中的表演方法觸發我們的通知時,需要的切點表達式:
execution(* com.spring.learn.index.Performance.perform(..))
若我們僅僅需要的是com包下的類,可以使用within()來限制匹配:
execution(* com.spring.learn.index.Performance.perform(..)) && within(com.*)
使用 && 來表示與關系;同理 || 表示或關系;!表示非。
註在XML中使用and or not 來替換(因為符號在XML中有特殊含義)。
在切點中選擇bean
除了上述指示器外Spring還引入了bean()指示器,bean()使用bean ID或者bean名稱來作為參數限制切點只匹配特定的bean。
使用註解創建切面
已經定義了Performance接口,它是切面中目標對象。使用AspecJ註解來定義切面。
定義切面:
使用Audience類:觀看演出的切面:
@Aspect
public class Audience {
//這是表演之前
@Before("execution(* com.spring.learn.index.Performance.perform(..))")
public void silenceCellPhones(){
System.out.println("觀眾的手機靜音");
}
//表演之前
@Before("execution(* com.spring.learn.index.Performance.perform(..))")
public void takeSeats() {
System.out.println("觀眾落座");
}
//表演之後
@AfterReturning("execution(* com.spring.learn.index.Performance.perform(..))")
public void applause() {
System.out.println("掌聲雷動");
}
//表演失敗
@AfterThrowing("execution(* com.spring.learn.index.Performance.perform(..))")
public void demandRefund() {
System.out.println("要求退款");
}
}
@Aspect註解,表明了Audience不僅僅是一個POJO,還是一個切面。
Audience中的方法使用註解的方式定義了通知何時調用。AspectJ中提供了五個註解來定義通知:
每一個註解都使用了切點表達式來作為他的值。但是我們的切點表達式重復使用了四次,其實我們可以只寫一次,然後使用引用的方式實現同樣的操作。
@PointCut註解能夠在一個@Aspect定義的切面內定義可重復使用的切點:
@Aspect
public class Audience {
//定義命名的切點
@Pointcut("execution(* com.spring.learn.index.Performance.perform(..))")
public void myPointCut(){}
@Before("myPointCut()")
public void silenceCellPhones(){
System.out.println("觀眾的手機靜音");
}
@Before("myPointCut()")
public void takeSeats() {
System.out.println("觀眾落座");
}
@AfterReturning("myPointCut()")
public void applause() {
System.out.println("掌聲雷動");
}
@AfterThrowing("myPointCut()")
public void demandRefund() {
System.out.println("要求再來一場新的表演");
}
}
到此為止Audience仍只是一個普通的POJO類,即使使用了@Aspect註解也不會被視為切面,這些註解不會解析,也不會創建將其轉化為切面的代理。
需要額外的配置類,並在配置類上使用EnableAspectJ-AutoProxy 註解啟動自動代理功能:
//啟用AspectJ自動代理
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AspectConfig {
//聲明Audience bean
@Bean
public Audience audience() {
return new Audience();
}
}
如果使用的是XML來裝配bean的話,需要使用Spring AOP命名空間的<aop:aspectj-autoproxy> 元素:
不論哪種方式,AspectJ自動代理都會使用@Aspect註解的bean創建一個代理,這個代理會圍繞所有該切面的切點所匹配的bean。將會為bean創建一個代理,使通知的方法在切點前後被調用。
創建環繞通知
@Aspect
public class Audience {
//定義命名的切點
@Pointcut("execution(* com.spring.learn.index.Performance.perform(..))")
public void myPointCut(){}
@Around("myPointCut()")
public void watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println("觀眾的手機靜音");
System.out.println("觀眾落座");
jp.proceed();
System.out.println("掌聲雷動");
}catch (Throwable e) {
System.out.println("退票退票");
}
}
}
可以看到環繞方法可以實現之前的幾個註解的所有功能。
註:ProceedingJoinPoint參數,這個參數必須有,因為要在通知中通過它來調用被通知的方法。通知方法可以做任何事,當要將控制權交給被通知的方法時,需要調用ProceedingJoinPoint的proceed()方法。
註: 一定要調用proceed()方法,否則通知將會阻塞被通知方法的調用。
實際上你也可以對被通知方法進行多次調用。這一般是為了實現重試邏輯。
處理通知中的參數
場景:磁帶中不同的磁道有多種歌曲,調用playTrack()方法可以實現播放。要記錄每個磁道被播放的次數,使用切面來完成:
通過上述的方式可以將切面上的參數同步到通知上。
通過註解引入新功能
通過引入的AOP概念,切面可以為Spring bean添加新的方法。
我們為Performance接口引入下面的接口:
public interface Encoreable {
void perforEncore();
}
則定義的切面如下:
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value = "com.spring.learn.index.Performance+",
defaultImpl = DefaultEncoreable.class)
public static Encoreable encoreable;
}
註:和其他切面一樣,我們需要將在Spring應用中將Encoreableintroducer聲明為一個bean:
//啟用AspectJ自動代理
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AspectConfig {
//聲明Audience bean
@Bean
public Audience audience() {
return new Audience();
}
//聲明EncoreableIntroducer bean
@Bean
public EncoreableIntroducer encoreableIntroducer() {
return new EncoreableIntroducer();
}
}
或:
由於目前已經不接觸XML配置了。
所以XML配置與AspectJ的更強大面向切面實現,等過一陣再來補充。
AspectJ的全面學習博客地址。
本文內容是書中內容兼具自己的個人看法所成。可能在個人看法上會有諸多問題(畢竟知識量有限,導致認知也有限),如果讀者覺得有問題請大膽提出,我們可以相互交流、相互學習,歡迎你們的到來,心成意足,等待您的評價。
Spring基礎知識之基於註解的AOP