【Spring實戰(zhàn)】面向切面編程

本章內容:

  • 面向切面編程的基本原理
  • 通過POJO創(chuàng)建切面
  • 使用@AspectJ注解
  • 為AspectJ切面注入依賴

軟件系統(tǒng)中的一些功能需要用到應用程序的多個地方,但是我們又不想在每個點都明確調用它們。日志、安全和事務管理的確都很重要,但它們不應是應用對象主動參與的行為,應該要讓應用對象只關注于自己所針對的業(yè)務領域問題,其他方面的問題由其他應用對象來處理。

在軟件開發(fā)中,散布于應用中多處的功能被稱為橫切關注點(cross-cutting concern)。通常,這些橫切關注點從概念上與應用的業(yè)務邏輯分離的(但是往往會直接嵌入到應用的業(yè)務邏輯之中)。把這些橫切關注點與業(yè)務邏輯相分離正是面向切面編程(AOP)所要解決的問題。

裝配Bean介紹了如何使用依賴注入(DI)管理和配置我們的應用對象。DI有助于應用對象之間的解耦,而AOP可以實現(xiàn)橫切關注點與它們所影響的對象之間的解耦。

本章展示了Spring對切面的支持,包括如何把普通類聲明為一個切面和如何使用注解創(chuàng)建切面。除此之外,還會看到AspectJ(另一種流行的AOP實現(xiàn))如何補充AOP框架的功能。

什么是面向切面編程

切面能幫助我們模塊化橫切關注點。橫切關注點可以被描述為影響應用多處的功能。下圖呈現(xiàn)了橫切關注點的概念:

切面實現(xiàn)了橫切關注點(跨多個應用對象的邏輯)的模塊化

上圖展現(xiàn)了一個被劃分為模塊的典型應用。每個模塊的核心功能都是為特定業(yè)務領域提供服務,但是這些模塊都需要類似的輔助功能。

重用通用功能最常見的面向對象技術是繼承(inheritance)或委托(delegation)。但是,如果在整個應用中都使用相同的基類,繼承往往會導致一個脆弱的對象體系;而使用委托可能需要對委托對象進行復雜的調用。

切面提供了取代繼承和委托的另一種可選方案,而且在很多場景下更清晰簡潔。在使用面向切面編程時,仍然在一個地方定義通用功能,但是可以通過聲明的方式定義這個功能要以何種方式在何處應用,不需要修改受影響的類。橫切關注點可以被模塊化為特殊的類,這些類被稱為切面(aspect)。

這樣做有兩個好處:

  1. 現(xiàn)在每個關注點都集中于一個地方,而不是分散到多處代碼中;
  2. 服務模塊更簡潔,因為它們只包含主要關注點(或核心功能)的代碼,而次要關注點的代碼被轉移到切面中了。
定義AOP術語

描述切面的常用術語有通知(advice)、切點(pointcut)和連接點(join point)。下圖展示了這些概念是如何關聯(lián)在一起的:

在一個或多個連接點上,可以把切面的功能(通知)織入到程序的執(zhí)行過程中
通知(Advice)

在AOP術語中,切面要完成的工作被稱為通知。

通知定義了切面是什么以及何時使用。除了描述切面要完成的工作,通知還解決了何時執(zhí)行這個工作的問題。

Spring切面可以應用5種類型的通知:

  • 前置通知(Before):在目標方法被調用之前調用通知功能;
  • 后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么;
  • 返回通知(After-returning):在目標方法成功執(zhí)行之后調用通知;
  • 異常通知(After-throwing):在目標方法拋出異常后調用通知;
  • 環(huán)繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執(zhí)行自定義的行為。
連接點(Join point)

應用可能有數(shù)以千計的時機應用通知。這些時機被稱為連接點。連接點是在應用執(zhí)行過程中能夠插入切面的一個點。切面代碼可以利用這些點插入到應用的正常流程之中,并添加新的行為。

切點(Poincut)

一個切面并不需要通知應用的所有連接點。切點有助于縮小切面所通知的連接點的范圍。

如果說通知定義了切面的“什么”和“何時”的話,那么切點就定義了“何處”。切點的定義會匹配通知所要織入的一個或多個連接點。通常使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。有些AOP框架允許創(chuàng)建動態(tài)的切點,可以根據運行時的決策來決定是否應用通知。

切面(Aspect)

切面是通知和切點的結合。通知和切點共同定義了切面的全部內容——它是什么,在何時和何處完成其功能。

引入(Introduction)

引入允許向現(xiàn)有的類添加新方法或屬性。從而可以在無需修改這些現(xiàn)有的類的情況下,讓它們具有新的行為和狀態(tài)。

織入(Weaving)

織入是把切面應用到目標對象并創(chuàng)建新的代理對象的過程。切面在指定的連接點被織入到目標對象中。在目標對象的生命周期里有多個點可以進行織入:

  • 編譯期:切面在目標類編譯時被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
  • 類加載期:切面在目標類加載到JVM時被織入。這種方式需要特殊的類加載器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的字節(jié)碼。AspectJ 5的加載時織入(load-time weaving,LTW)就支持以這種方式織入切面。
  • 運行期:切面在應用運行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標對象動態(tài)地創(chuàng)建一個代理對象。Spring AOP就是以這種方式織入切面的。
Spring對AOP的支持

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

  • 基于代理的經典Spring AOP;
  • 純POJO切面;
  • @AspectJ注解驅動的切面;
  • 注入式AspectJ切面(適用于Spring各版本)。

前三種都是Spring AOP實現(xiàn)的變體,Spring AOP構建在動態(tài)代理基礎之上,因此,Spring對AOP的支持局限于方法攔截。

Spring的經典AOP編程模型曾經的確很棒,但是現(xiàn)在Spring提供了更簡潔和干凈的面向切面編程方式。。引入了簡單的聲明式AOP和基于注解的AOP之后,Spring經典的AOP看起來就顯得非常笨重和過于復雜,直接使用ProxyFactory Bean會讓人感覺厭煩。

借助Spring的aop命名空間,可以將純POJO轉換為切面。實際上這些POJO只是提供了滿足切點條件時所要調用的方法。這種技術需要XML配置。

Spring借鑒了AspectJ的切面,以提供注解驅動的AOP。本質上依然是Spring基于代理的AOP,但是編程模型幾乎與編寫成熟的AspectJ注解切面完全一致。這種AOP風格的好處在于能夠不使用XML來完成功能。

如果AOP需求超過了簡單的方法調用,那么需要考慮使用AspectJ來實現(xiàn)切面。注入式AspectJ切面能夠幫助你將值注入到AspectJ驅動的切面中。

開始學習Spring AOP技術之前,必須要了解Spring AOP框架的一些關鍵知識。

Spring通知是Java編寫的

Spring所創(chuàng)建的通知都是用標準的Java類編寫的。這樣就可以使用與普通Java開發(fā)一樣的集成開發(fā)環(huán)境(IDE)來開發(fā)切面。定義通知所應用的切點通常會使用注解或在Spring配置文件里采用XML來編寫。

AspectJ與之相反。AspectJ最初是以Java語言擴展的方式實現(xiàn)的。

Spring在運行時通知對象

通過在代理類中包裹切面,Spring在運行期把切面織入到Spring管理的bean中。如下圖所示:

Spring的切面由包裹了目標對象的代理類實現(xiàn)。代理類處理方法的調用,執(zhí)行額外的切面邏輯,并調用目標方法

代理類封裝了目標類,并攔截被通知方法的調用,再把調用轉發(fā)給真正的目標bean。當代理攔截到方法調用時,在調用目標bean方法之前,會執(zhí)行切面邏輯。

直到應用需要被代理的bean時,Spring才創(chuàng)建代理對象。如果使用的是ApplicationContext的話,在ApplicationContext從BeanFactory中加載所有bean的時候,Spring才會創(chuàng)建被代理的對象。

Spring只支持方法級別的連接點

通過使用各種AOP方案可以支持多種連接點模型。因為Spring基于動態(tài)代理,所以Spring只支持方法連接點。Spring缺少對字段連接點的支持,無法讓我們創(chuàng)建細粒度的通知,例如攔截對象字段的修改。而且它不支持構造器連接點,我們就無法在bean創(chuàng)建時應用通知。

方法攔截可以滿足絕大部分的需求。其他連接點攔截功能可以利用Aspect來補充Spring AOP的功能。

通過切點來選擇連接點

切點用于準確定位應該在什么地方應用切面的通知。

在Spring AOP中,要使用AspectJ的切點表達式語言來定義切點。

是Spring僅支持AspectJ切點指示器(pointcut designator)的一個子集。因為Spring是基于代理的,而某些切點表達式是與基于代理的AOP無關的。

下表列出了Spring AOP所支持的AspectJ切點指示器:

Spring借助AspectJ的切點表達式語言來定義Spring切面

嘗試使用AspectJ其他指示器時,將會拋出IllegalArgument-Exception異常。

這些Spring支持的指示器,只有execution指示器是實際執(zhí)行匹配的,而其他的指示器都是用來限制匹配的。

編寫切面

定義一個Performance接口:

package concert;

public interface Performance{
    public void perform();
}

Performance可以代表任何類型的現(xiàn)場表演。假設想編寫Performance的perform()方法觸發(fā)的通知。下圖展現(xiàn)了一個切點表達式,這個表達式設置當perform()方法執(zhí)行時觸發(fā)通知的調用。

使用AspectJ切點表達式來選擇Performance的perform()方法

我們使用execution()指示器選擇Performance的perform()方法。方法表達式以“*”號開始,表明不關心方法返回值的類型。然后,指定了全限定類名和方法名。對于方法參數(shù)列表,使用兩個點號(..)表明切點選擇任意的perform()方法,無論該方法的入參是什么。

假設需要配置的切點僅匹配concert包??梢允褂脀ithin()指示器來限制匹配:

使用within()指示器限制切點范圍

注意使用了“&&”操作符把execution()和within()指示器連接在一起形成與(and)關系。類似地,可以使用“||”操作符來標識或(or)關系,使用“!”操作符來標識非(not)操作。

由于“&”在XML中有特殊含義,在Spring的XML配置里面描述切點時,可以使用and來代替“&&”。同樣,or和not可以分別用來代替“||”和“!”。

在切點中選擇bean

除了上表所示的AspectJ指示器,Spring還引入了一個新的bean()指示器,它允許我們在切點表達式中使用bean的ID來標識bean。bean()使用Bean ID或bean名稱作為參數(shù)來限制切點只匹配特定的bean。

例如:

excution(* concert.Performance.perform()) 
    and bean('woodstock')

在執(zhí)行Performance的perform()方法時應用通知,限定的bean的ID為woodstock。

還可以使用非操作除了特定ID以外的其他bean應用通知:

excution(* concert.Performance.perform()) 
    and !bean('woodstock')

切面的通知會被編織到所有ID不為woodstock的bean中。

使用注解創(chuàng)建切面

使用注解來創(chuàng)建切面是AspectJ 5所引入的關鍵特性。AspectJ 5之前,編寫AspectJ切面需要學習一種Java語言的擴展,AspectJ面向注解的模型可以非常簡便地通過少量注解把任意類轉變?yōu)榍忻妗?/p>

定義切面

從演出的角度來看,觀眾非常重要,但是對演出本身的功能來講,它并不是核心,這是一個單獨的關注點。因此,將觀眾定義為一個切面,并將其應用到演出上就是較為明智的做法。

定義一個Audience類,它定義了我們所需的一個切面:

Audience切面定義

Audience類使用@AspectJ注解進行了標注。該注解表明Audience是一個切面。Audience類中的方法都使用注解來定義切面的具體行為。

Audience有四個方法,定義了一個觀眾在觀看演出時可能會做的事情。這些方法都使用了通知注解來表明它們應該在什么時候調用。AspectJ提供了五個注解來定義通知,如下表所示:

使用AspectJ注解來聲明通知方法

Audience使用到了前面五個注解中的三個。takeSeats()silenceCellPhones()方法都用到了@Before注解,表明它們應該在演出開始之前調用。applause()方法使用了@AfterReturning注解,它會在演出成功返回后調用。demandRefund()方法上添加了@AfterThrowing注解,這表明它會在拋出異常以后執(zhí)行。

代碼中所有的這些注解都給定了一個切點表達式作為它的值,這四個方法的切點表達式都是相同的。如果我們只定義這個切點一次,然后每次需要的時候引用它,效果可能會更好。

@Pointcut注解能夠在一個@AspectJ切面內定義可重用的切點

通過@Pointcut注解聲明頻繁使用的切點表達式

在Audience中,performance()方法使用了@Pointcut注解。為@Pointcut注解設置的值是一個切點表達式。通過在performance()方法上添加@Pointcut注解,擴展了切點表達式語言,這樣就可以在任何的切點表達式中使用performance()。

performance()方法的實際內容并不重要,該方法本身只是一個標識,供@Pointcut注解依附。

需要注意的是,除了注解和沒有實際操作的performance()方法,Audience類依然是一個POJO。能夠像使用其他的Java類那樣調用它的方法,它的方法也能夠獨立地進行單元測試。

像其他的Java類一樣,它可以裝配為Spring中的bean:

@Bean

public Audience audience() {
    return new Audience();
}

但是目前Audience仍然只是Spring容器中的一個bean。即便使用了AspectJ注解,也并不會被視為切面,這些注解不會解,也不會創(chuàng)建將其轉換為切面的代理。

Java Config啟用AspectJ注解的自動代理

暫時略過

XML啟用AspectJ自動代理

要使用XML來裝配bean的話,那么需要使用Spring aop命名空間中的<aop:aspectj-autoproxy>元素:

在XML中,通過aop命名空間啟用AspectJ自動代理

AspectJ自動代理會為使用@Aspect注解的bean創(chuàng)建一個代理,這個代理會圍繞著所有該切面的切點所匹配的bean。

,Spring的AspectJ自動代理僅僅使用@Aspect作為創(chuàng)建切面的指導,切面依然是基于代理的。本質上依然是Spring基于代理的切面。

創(chuàng)建環(huán)繞通知

環(huán)繞通知是最為強大的通知類型。它能夠讓你所編寫的邏輯將 被通知的目標方法完全包裝起來。就像在一個通知方法中同時編寫前置通知和后置通知。

重新實現(xiàn)Audience切面:

環(huán)繞通知Audience

@Around注解表明watchPerformance()方法會作為performance()切點的環(huán)繞通知。在這個通知中,觀眾在演出之前會將手機調至靜音并就坐,演出結束后會鼓掌喝彩。如果演出失敗的話,觀眾會要求退款。

新的通知方法接受ProceedingJoinPoint作為參數(shù),這個對象是必要的,因為要在通知中通過它來調用被通知的方法。通知方法中當要將控制權交給被通知的方法時,調用ProceedingJoinPoint的proceed()方法。

如果不調proceed()方法,通知實際上會阻塞對被通知方法的調用。

處理通知中的參數(shù)

目前為止編寫的切面都很簡單,沒有任何參數(shù)。如果切面所通知的方法確實有參數(shù)該怎么辦呢?切面怎么訪問和使用傳遞給被通知方法的參數(shù)?

重新看一下裝配Bean章節(jié)的BlankDisc樣例:

package soundsystem.properties;

import java.util.List;

import soundsystem.CompactDisc;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;
  private List<String> tracks;

  public void setTitle(String title) {
    this.title = title;
  }

  public void setArtist(String artist) {
    this.artist = artist;
  }

  public void setTracks(List<String> tracks) {
    this.tracks = tracks;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }

}

play()方法會循環(huán)所有的磁道并調用playTrack()方法。但是,我們也可以創(chuàng)建一個playTrack()方法直接播放某一個磁道中的歌曲。

假設想記錄每個磁道被播放的次數(shù)。一種方法就是修改playTrack()方法,直接在每次調用的時候記錄這個數(shù)量。但是,記錄磁道的播放次數(shù)與播放本身是不同的關注點,因此不應該屬于playTrack()方法。這應該是切面要完成的任務。

為了記錄每個磁道所播放的次數(shù),創(chuàng)建了TrackCounter類,它是是通知playTrack()方法的一個切面:

使用參數(shù)化的通知來記錄磁道播放的次數(shù)

切面使用@Pointcut注解定義命名的切點,并使用@Before將一個方法聲明為前置通知。這里的不同點在于切點還聲明了要提供給通知方法的參數(shù)。

在切點表達式中聲明參數(shù),這個參數(shù)傳入到通知方法中

需要關注的是切點表達式中的args(trackNumber)限定符。它表明傳遞給playTrack()方法的int類型參數(shù)也會傳遞到通知中去。參數(shù)的名稱trackNumber也與切點方法簽名中的參數(shù)相匹配。

這個參數(shù)會傳遞到通知方法中,這個通知方法通過@Before注解和命名切點trackPlayed(trackNumber)定義的。切點定義中的參數(shù)與切點方法中的參數(shù)名稱是一樣的,這樣就實現(xiàn)了從命名切點到通知方法的參數(shù)轉移。

現(xiàn)在,可以在Spring配置中將BlankDisc和TrackCounter定義為bean,并啟用AspectJ自動代理:

配置TrackCount記錄每個磁道播放的次數(shù)

編寫一個簡單測試。播放幾個磁道并通過TrackCounter斷言播放的數(shù)量。

測試TrackCounter切面

目前為止,所使用的切面中,所包裝的都是被通知對象的已有方法。方法包裝僅僅是切面所能實現(xiàn)的功能之一。接下來看如何通過編寫切面,為被通知的對象引入全新的功能。

通過注解引入新功能

我們還沒有為對象增加任何新的方法,但是已經為對象擁有的方法添加了新功能。如果切面能夠為現(xiàn)有的方法增加額外的功能,為什么不能為一個對象增加新的方法呢?利用被稱為引入的AOP概念,切面可以為Spring bean添加新方法。

在Spring中,切面只是實現(xiàn)了它們所包裝bean相同接口的代理。如果除了實現(xiàn)這些接口,代理也能暴露新接口的話,切面所通知的bean看起來像是實現(xiàn)了新的接口,即便底層實現(xiàn)類并沒有實現(xiàn)這些接口也無所謂。

使用Spring AOP,可以為bean引入新的方法。代理攔截調用并委托給實現(xiàn)該方法的其他對象

當引入接口的方法被調用時,代理會把此調用委托給實現(xiàn)了新接口的某個其他對象。實際上,一個bean的實現(xiàn)被拆分到了多個類中。

為了驗證這個思路,為示例中的所有的Performance實現(xiàn)引入下面的Encoreable接口:

package concert;

public interface Encoreable {
    void performEncore();
}

需要有一種方式將這個接口應用到Performance實現(xiàn)中?,F(xiàn)在假設能夠訪問Performance的所有實現(xiàn),并對其進行修改,讓它們都實現(xiàn)Encoreable接口。從設計的角度來看,這并不是最好的做法,并不是所有的Performance都是具有Encoreable特性的。另外一方面,有可能無法修改所有的Performance實現(xiàn)。

借助于AOP的引入功能,我們可以不必在設計上妥協(xié)或者侵入性地改變現(xiàn)有的實現(xiàn)。為了實現(xiàn)該功能,創(chuàng)建一個新的切面:

EncoreableIntroducer是一個切面。它與之前所創(chuàng)建的切面不同,它并沒有提供前置、后置或環(huán)繞通知,而是通過@DeclareParents注解,將Encoreable接口引入到Performance bean中。

@DeclareParents注解由三部分組成:

  • value屬性指定了哪種類型的bean要引入該接口。在本例中,是所有實現(xiàn)Performance的類型。(標記符后面的加號表示是Performance的所有子類型,而不是Performance本身。)
  • defaultImpl屬性指定了為引入功能提供實現(xiàn)的類。在這里指定的是DefaultEncoreable提供實現(xiàn)。
  • @DeclareParents注解所標注的靜態(tài)屬性指明了要引入了接口。在這里,我們所引入的是Encoreable接口。

和其他的切面一樣,我們需要在Spring應用中將EncoreableIntroducer聲明為一個bean:

<bean class="concert.EncoreableIntroducer" />

Spring的自動代理機制將會獲取到它的聲明,當Spring發(fā)現(xiàn)一個bean使用了@Aspect注解時,Spring就會創(chuàng)建一個代理,然后將調用委托給被代理的bean或被引入的實現(xiàn),這取決于調用的方法屬于被代理的bean還是屬于被引入的接口。

Spring注解和自動代理提供了一種很便利的方式來創(chuàng)建切面。但是面向注解的切面聲明有一個明顯的劣勢:必須能夠為通知類添加注解。為了做到這一點,必須要有源碼。

如果沒有源碼的話,或者不想將AspectJ注解放到代碼之中,Spring為切面提供了另外一種可選方案。

在XML中聲明切面

如果需要聲明切面,但是又不能為通知類添加注解的時候,就必須轉向XML配置。

在Spring的aop命名空間中,提供了多個元素用來在XML中聲明切面。

image.png

aop命名空間的其他元素能夠直接在Spring配置中聲明切面,而不需要使用注解。

現(xiàn)在將Audience類的所有AspectJ注解全部移除掉:

盡管看起來并沒有什么差別,但Audience已經具備了成為AOP通知的所有條件。

聲明前置和后置通知

使用Spring aop命名空間中的一些元素,將沒有注解的Audience類轉換為切面:

通過XML將無注解的Audience聲明為切面

在<aop:config>元素內,可以聲明一個或多個通知器、切面或者切點。在上面的例子中,使用<aop:aspect>元素聲明了一個簡單的切面。ref元素引用了一個POJO bean,該bean實現(xiàn)了切面的功能。ref元素所引用的bean提供了在切面中通知所調用的方法。

第一個需要注意的事項是大多數(shù)的AOP配置元素必須在<aop:config>元素的上下文內使用。這條規(guī)則有幾種例外場景,但是把bean聲明為一個切面時,總是從<aop:config>元素開始配置的。

示例切面應用了四個不同的通知。兩個<aop:before>元素定義了匹配切點的方法執(zhí)行之前調用前置通知方法(由method屬性聲明)。<aop:after-returning>元素定義了一個返回(after-returning)通知,在切點所匹配的方法調用之后再調用后置通知方法。<aop:after-throwing>元素定義了異常(after-throwing)通知,如果所匹配的方法執(zhí)行時拋出任何的異常,都將調用demandRefund()方法。下圖展示了通知邏輯如何織入到業(yè)務邏輯中:

Audience切面包含四種通知,它們把通知邏輯織入進匹配切面切點的方法中

在所有的通知元素中,pointcut屬性定義了通知所應用的切點,它的值是使用AspectJ切點表達式語法所定義的切點。

在基于AspectJ注解的通知中,當發(fā)現(xiàn)這種類型的重復時,我們使用@Pointcut注解消除了這些重復的內容。在基于XML的切面聲明中,需要使用<aop:pointcut>元素。

如下的XML展現(xiàn)了如何將通用的切點表達式抽取到一個切點聲明中,,這樣這個聲明就能在所有的通知元素中使用了。

使用<aop:pointcut>定義命名切點

現(xiàn)在切點在一個地方定義的,并被多個通知元素引用。<aop:pointcut>元素定義了一個id為performance的切點。同時修改所有的通知元素,用 pointcut-ref 屬性來引用這個命名切點。

聲明環(huán)繞通知

前置通知和后置通知有一些限制。如果不使用成員變量存儲信息,在前置通知和后置通知之間共享信息會非常麻煩。

希望并報告每個節(jié)目表演了多長時間。使用前置通知和后置通知實現(xiàn)該功能的唯一方式是在前置通知中記錄開始時間并在某個后置通知中報告表演耗費的時間。這樣的話我們必須在一個成員變量中保存開始時間。因為Audience是單例的,如果像這樣保存狀態(tài)的話,將會存在線程安全問題。

環(huán)繞通知相比于前置通知和后置通知在這點上有明顯的優(yōu)勢,使用環(huán)繞通知,我們可以完成前置通知和后置通知所實現(xiàn)的相同功能,且只需要在一個方法中實現(xiàn)。由于整個通知邏輯是在一個方法內實現(xiàn)的,所以不需要使用成員變量保存狀態(tài)。

修改watchPerformance類:

watchPerformance()方法提供了AOP環(huán)繞通知

在切面中,watchPerformance()方法包含了之前四個通知方法的所有功能。且所有的功能都放在了這一個方法中,因此這個方法還要負責自身的異常處理。

聲明環(huán)繞通知所需要做的僅僅是使用<aop:around>元素。

在XML中使用<aop:around>元素聲明環(huán)繞通知
使用XML為通知傳遞參數(shù)

使用XML來配置切面,看一下如何完成這個任務。

首先,要移除掉TrackCounter上所有的@AspectJ注解。

無注解的TrackCounter

去掉@AspectJ注解后,除非顯式調用countTrack()方法,否則TrackCounter不會記錄磁道播放的數(shù)量。借助一點Spring XML配置,能夠讓TrackCounter重新變?yōu)榍忻妗?/p>

如下的程序展現(xiàn)了完整的Spring配置,在這個配置中聲明了TrackCounter bean和BlankDisc bean,并將TrackCounter轉化為切面:

在XML中將TrackCounter配置為參數(shù)化的切面

切點表達式中包含了一個參數(shù),這個參數(shù)會傳遞到通知方法中。(不使用“&&”是因為在XML中,“&”符號會被解析為實體的開始)。

通過切面引入新的功能

AOP引入并不是AspectJ特有的。使用Spring aop命名空間中的<aop:declare-parents>元素,可以實現(xiàn)相同的功能。

下面的XML代碼片段與之前基于AspectJ的引入功能相同:

<aop:declare-parents>聲明了此切面所通知的bean要在它的對象層次結構中擁有新的父類型。類型匹配Performance接口(由types-matching屬性指定)的那些bean在父類結構中會增加Encoreable接口(由implementinterface屬性指定)。最后要解決的問題是Encoreable接口中的方法實現(xiàn)要來自于何處。

有兩種方式標識所引入接口的實現(xiàn)。本例中,使用default-impl屬性用全限定類名顯式指定Encoreable的實現(xiàn)。還可以使用delegate-ref屬性來標識。

delegate-ref屬性引用了一個Spring bean作為引入的委托。需要在Spring上下文中存在一個ID為encoreableDelegate的bean。

<bean id="encoreableDelegate"
    class="concert.DefaultEncoreable" />

使用default-impl來直接標識委托和間接使用delegate-ref的區(qū)別在于后者是Spring bean,它本身可以被注入、通知或使用其他的Spring配置。

注入AspectJ切面

暫時跳過

小結

AOP是面向對象編程的一個強大補充。通過AspectJ,現(xiàn)在可以把之前分散在應用各處的行為放入可重用的模塊中。這有效減少了代碼冗余,并讓我們的類關注自
身的主要功能。

Spring提供了一個AOP框架,把切面插入到方法執(zhí)行的周圍。

關于在Spring應用中如何使用切面,可以有多種選擇。

當Spring AOP不能滿足需求時,我們必須轉向更為強大的AspectJ。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,694評論 19 139
  • 在生活中,監(jiān)控用電量是一個很重要的功能,但并不是大多數(shù)家庭重點關注的問題。軟件系統(tǒng)的一些功能就像家里的電表一樣,這...
    yjaal閱讀 641評論 0 3
  • 幸福,不是長生不老,不是大魚大肉,不是權傾朝野。幸福是每一個微小的生活愿望達成,如果人生沒有天長地久,那就珍惜曾經...
    大樹姑娘閱讀 372評論 6 6
  • 以勝負來評定一場戰(zhàn)役,是最直接,也是最模糊的。 按目的的達成與否,農民無疑是勝利的。 因為在農民的心里,這僅僅是農...
    會跳舞的西紅柿閱讀 398評論 0 0
  • 前幾天,偶然重讀《七色花》這篇小學教科書的文章,大體上講了一個小女孩突然擁有了一朵七色花,一片花瓣能實現(xiàn)一個愿望,...
    一粒粒Eli閱讀 333評論 0 0

友情鏈接更多精彩內容