Spring AOP初步總結(一)

學習AOP有段時間了,一直沒空總結一下,致使有些知識點都遺忘了,以後會把之前學過的Spring核心相關的知識點總結一輪...java

 

先大致介紹下Spring AOP的特色(均摘自"Spring in action第四版"):正則表達式

  Spring支持了AOP,另外還有不少實現了AOP的技術,例如AspectJ,它補充了Spring AOP框架的功能,他們之間有着大量的協做,並且Spring AOP中大量借鑑了AspectJ項目,Spring AOP相對粗粒度,而AspectJ提供更強大更細粒度的控制,以及更豐富的AOP工具集,但須要額外的語法學習;spring

  Spring借鑑了AspectJ的切面,以提供註解驅動的AOP,編程模型幾乎與編寫成熟的AspectJ註解切面徹底一致。這種AOP風格的好處在於可以不使用XML來完成功能。Spring AOP構建在動態代理之上,所以,Spring對AOP的支持侷限於方法攔截;若是你對AOP的需求超過了建黨方法調用(如構造器或屬性攔截),那麼你須要AspectJ來實現切面;編程

  Spring提供了4種類型的AOP支持:app

    基於代理的經典Spring AOP;框架

    純POJO切面;工具

    @AspectJ註解驅動的切面;學習

    注入式AspectJ切面(適用於Spring各版本);spa

 

 

AOP相關的術語(概念):3d

  1.通知(Advice):

    切面的工做被稱爲通知,通知定義了切面是什麼以及什麼時候使用;

    分5類:

前置通知(Before) 在目標方法被調用以前調用通知功能;
後置通知(After) 在目標方法完成以後調用通知,此時不會關心方法的輸出是什麼;
返回通知(After-returning) 在目標方法成功執行以後調用通知;
異常通知(After-throwing) 在目標方法拋出異常後調用通知;
環繞通知(Around)

通知包裹了被通知的方法,在被通知的方法調用以前和調用以後
執行自定義的行爲。

 

  2.鏈接點(Join point)

    應用通知的時機被稱爲鏈接點;鏈接點是在應用執行過程當中可以插入切面的一個點。這個點能夠是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼能夠利用這些點插入到應用的正常流程之中,並添加新的行爲;

 

  3.切點(Poincut)

    切點定義了切面的在何處實施;一般使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名來指定;

 

  4.切面Aspect

    是通知(什麼時候,作什麼)和切點(何處)的結合;

 

  5.引入(Introduction)

    容許咱們向現有的類添加新方法或新屬性;

 

  6.織入(Weaving)

    把切面應用到目標對象並建立新的代理對象的過程

    3種織入時機:

編譯期 在目標類編譯時被織入,AspectJ的織入編譯器就是以這種方式織入的
類加載期 在目標類加載到JVM中時被織入,AspectJ5的加載時織入(load-time weaving,LTW)支持
運行期 在運行的某個時刻被織入,通常狀況下AOP容器會爲目標對象動態建立一個代理對象,Spring AOP就支持這種方式

    

Spring AOP是基於動態代理的:

  經過在代理類中包裹切面,Spring在運行期把切面織入到Spring管理的bean中。以下圖所
示,代理類封裝了目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。當
代理攔截到方法調用時,在調用目標bean方法以前,會執行切面邏輯。

  

 

編寫切點(若干圖例瞭解切點表達式):

   

  

    咱們使用execution()指示器選擇Performance的perform()方法。方法表達式以「*」號開始,代表了咱們不關心方法返回值的類型。而後,咱們指定了全限定類名和方法名。對於方法參數列表,咱們使用兩個點號(..)代表切點要選擇任意的perform()方法,不管該方法的入參是什麼。

  

  咱們使用了「&&」操做符把execution()和within()指示器鏈接在一塊兒造成與(and)關係(切點必須匹配全部的指示器)。相似地,咱們可使用「||」操做符來標識或(or)關係,而使用「!」操做符來標識非(not)操做。由於「&」在XML中有特殊含義,因此在Spring的XML配置裏面描述切點時,咱們可使用and來代替「&&」。一樣,or和not能夠分別用來代替「||」和「!」。

  

  在這裏,咱們但願在執行Performance的perform()方法時應用通知,但限定bean的ID爲woodstock。

 

下面正式開始建立切面:

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class Audience {
    @Before("execution(** concert.Performance.perform(..))")
    public void silenceCellPhones() {
        System.out.println("Silencing cell phones");
     }

    @Before("execution(** concert.Performance.perform(..))")
    public void takeSeats() {
        System.out.println("Taking seats");
     }

      @AfterReturning("execution(** concert.Performance.perform(..))")
    public void applause() {
        System.out.println("CLAP CLAP CLAP...");
     }

    @AfterThrowing("execution(** concert.Performance.perform(..))")
    public void demandRefund() {
        System.out.println("Demanding a refund");
     }
}

  Audience類使用@AspectJ註解進行了標註。該註解代表Audience不只僅是一個POJO,仍是一個切面。Audience類中的方法都使用註解來定義切面的具體行爲。Audience有四個方法,定義了一個觀衆在觀看演出時可能會作的事情。在演出以前,觀衆要就坐(takeSeats())並將手機調至靜音狀態(silenceCellPhones();

  AspectJ提供了五個註解來定義通知

這樣,一個切面就定義好了,但咱們四個註解使用的表達式都同樣,能夠用@Pointcut註解提供表達式的引用:

  1.提供一個空方法,在上面增長註解:@Pointcut("execution(** concert.Performance.perform(..))");

  2.其它註解引用,例如: @Before("上面定義的空方法名()");

 

光這樣還不夠,下面要裝配Audience類成爲一個bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;


@Configuration
@EnableAspectJAutoProxy //氣筒AspectJ自動代理
@ComponentScan
public class ConcertConfig { //bean的配置類
    
    @Bean
    public Audience audience() { //配置Audience類成爲一個Bean return new Audience();
    }
}

 

 

以上就實現了前置後置分離通知的切面;

 

接下在介紹另外一種:環繞通知的寫法(邏輯不復雜的時候建議用這種方式,更加直觀,一旦邏輯複雜,可讀性會不好):

 

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Around;
import org.aspectj.lang.Aspect;
import org.aspectj.lang.Pointcut;

@Aspect
public void Audience {
    @Pointcut("execution(** concert.Performance.perform(..))")
    public void performance(){}

    @Around("performance()")
    public void watchPerformance(PerceedingJoinPoint jp){
        try {
            System.out.println("Silencing cell phones");
            System.out.println("Taking seats");
            jp.proceed();                   //調用被通知的方法(能夠屢次調用)
            System.out.println("CLAP CLAP CLAP...");
        } catch (Throwable e) {
            System.out.println("Demanding a refund");
        }
    }
}

 

以上總結的是不帶參數構造切面的狀況

 

接下來介紹若是在通知上符加參數的狀況:

import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;


@Aspect
public class TrackCounter {
    private Map<Integer, Interger> trackCounts = 
        new HashMap<Integer, Integer>(); 
    
    @Pointcut(
            "execution(* soundsystem.CompactDisc.playTrack(int))" +
            "&& args(trackNumber)")
    public void trackPlayed(int trackNumber) {}

    @Before("trackPlayed(trackNumber)")
    public void countTrack(int trackNumber){
        int currentCount = getPlayCOunt(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }

    public int getPlayCount(int trackNumber) {
        return trackCOunts.containKey(trackNumber) ?     
            trackCounts.get(trackNumber) : 0;
    }
}

圖例來解釋表達式:

相關文章
相關標籤/搜索