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的全面學習博客地址

AspectJ切入點語法詳解地址

 

本文內容是書中內容兼具自己的個人看法所成。可能在個人看法上會有諸多問題(畢竟知識量有限,導致認知也有限),如果讀者覺得有問題請大膽提出,我們可以相互交流、相互學習,歡迎你們的到來,心成意足,等待您的評價。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM