1. 程式人生 > >Spring基礎知識之基於註解的AOP

Spring基礎知識之基於註解的AOP

sdn 相互 com 目的 declare 裝配bean 四種 ace 裝配

背景概念:

  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