1. 程式人生 > >Spring AOP初步總結(一)

Spring AOP初步總結(一)

RoCE 實現 rman eight 實施 code 時間 前置 font

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

先大體介紹下Spring AOP的特點(均摘自"Spring in action第四版"):

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

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

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

    基於代理的經典Spring AOP;

    純POJO切面;

    @AspectJ註解驅動的切面;

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

復習下AOP相關的術語(概念):

  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;
    }
}

圖例來解釋表達式:

技術分享圖片

Spring AOP初步總結(一)