Spring之旅第七站:面向切面編程(AOP)

面向切面的Spring

本章主要內容:java

  • 面向切面編程的基本原理
  • 經過POJO建立切面
  • 使用@Aspect註解
  • 爲AspectJ切面注入依賴。

說明

若是你有幸能看到。git

  • 一、本文參考了《Spring 實戰》重點內容,參考了GitHub上的代碼
  • 二、本文只爲記錄做爲之後參考,要想真正領悟Spring的強大,請看原書。
  • 三、在一次佩服老外,國外翻譯過來的書,在GiuHub上大都有實例。看書的時候,跟着敲一遍,效果很好。
  • 四、代碼和筆記在這裏GitHub,對你有幫助的話,歡迎點贊。
  • 五、每一個人的學習方式不同,找到合適本身的就行。2018,加油。
  • 六、問候了下Java 8 In Action 的做者Mario Fusco,竟然回覆了。
  • 七、Spring In Action 、Spring Boot In Action的做者Craig Walls老忙了,沒理睬。
  • 八、知其然,也要知其因此然。

談一些我的感覺github

  • 一、趕快學習Spring吧,Spring MVC 、Spring Boot 、微服務。
  • 二、重點中的重點,學習JDK 8 Lambda,Stream,Spring 5 最低要求JDK1.8.
  • 三、還有Netty、放棄SH吧,否則你會落伍的。
  • 四、多看一些國外翻譯過來的書,例如 Xxx In Action 系列。權威指南系列。用Kindle~

軟件系統中的一些功能就像咱們家裏的電錶同樣。則核心功能須要用到應用程序的多個地方。可是咱們又不想在每一個點都明確調用它。日誌、安全、事務管理的確很重要。但它們是否爲應用對象主動參與的行爲呢?若是讓應用對象只關注與本身所針對的業務領域問題,而其餘方面的問題由其餘應用對象來處理,這樣不更好嗎?express

在軟件開發中,散佈於應用中多出功能被稱爲橫切關注點(crosscutting concern)。一般來說橫切關注點從概念上是與應用的業務邏輯分離的。但每每是耦合在一塊兒的,把這些橫切關注點與業務邏輯相分離正是面向切面編程(AOP)所要解決的問題。編程

依賴注入(DI)管理咱們的應用對象,DI有助於應用對象之間解耦。而AOP能夠實現橫切關注點與它們所影響的對象之間的耦合。安全

4.1 什麼是面向切面編程

切面可以幫咱們模塊化橫切關注點。簡而言之,橫切關注點能夠被描述爲影響應用多處的功能。例如 安全,事務、日誌等功能。app

若是要重用對象的話,最多見的面向對象技術是繼承、委託、組合。可是,若是整個應用中都使用相同的基類,繼承每每會致使一個脆弱的對象體系。而使用委託可能須要委託對象進行復雜的調用。框架

切面提供了取代繼承和委託的另外一種可選方案。在使用面向切面編程時,咱們仍然在一個地方定義通知功能,而無需修改受影響的類。橫切關注點能夠被模塊化爲特殊的類,這些類被稱爲切面(aspect). 這樣作帶來兩個好處:每一個關注點都集中到一個地方,而不是分散到多處代碼中:其次,服務模塊更簡潔,由於它只包含了主要關注點(核心功能)的代碼。而次要關注的代碼被移到切面中了。編程語言

4.1.1 定義AOP術語

描述切面的經常使用術語有:通知(advice)、切點(pointcut)、(鏈接點)。ide

通知(advice)

通知定義了切面是什麼以及什麼時候使用。除了描述切面要完成的工做外,通知還解決了什麼時候執行這個工做問題。它應該在某個方法被調用以前?以後?以前和以後都調用?仍是隻在方法拋出異常時調用?

Spring切面能夠應用5中類型的通知:

  • 前置通知(Before):在目標方法被調用以前調用通知功能。
  • 後置通知(After):在目標方法完成以後調用通知
  • 返回通知(After-returning):在目標方法成功執行以後調用通知
  • 異常通知(After-throwing):在目標方法拋出異常後調用通知
  • 環繞通知(Around):在被通知方法調用以前和調用以後執行自定義的行爲

鏈接點

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

切點 若是說通知定義了切面的的「什麼」和「什麼時候」,那麼切點定義了「何處」。切點的定義會匹配通知所要織入的一個或多個鏈接點。

切面 切面是通知和切點的結合。通知和切點經過定義了切面的所有 內容——他是什麼,在何時和在哪裏完成其功能。

引入 引入容許咱們向現有的類添加新的方法或者屬性。

織入

織入是把切面應用到目標對象並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象。在目標對象的生命週期裏有多個點能夠進行織入:

  • 編譯器:切面在目標類編譯時被織入。Aspect的織入編譯器就是以這種方式織入切面的。
  • 類加載器:切面在目標類加載到JVM時被織入。須要特殊的類加載(Classloader),它能夠在目標類被引入以前加強該目標類的字節碼(CGlib)
  • 運行期:切面在應用運行時的某個時刻被織入。AOP會爲目標對象建立一個代理對象

通知包含了須要用於多個應用對象的橫切關注點。鏈接點是程序執行過程當中可以應用通知的全部點。切點定義了通知被應用的具體位置(在哪些鏈接點),其中關鍵是切點定義了哪些鏈接點會獲得通知。

4.1.2 Spring對AOP的支持

並非全部的AOP框架都是相同的,他們在鏈接點模型上可能有強弱之分。有些容許在字段修飾符級別的通知,而另外一些只支持與方法調用相關的鏈接點。它們織入切面的方式和時機也有所不一樣。可是,不管如何,建立切點來定義切面所織入的鏈接點是AOP的基本功能。

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

  • 基於代理的經典Spring AOP
  • 純POJO切面
  • @AspectJ註解驅動的切面
  • 注入式AspectJ切面

前三種都是Spirng AOP實現的變體,Spring AOP構建在動態代理基礎上。所以,Spring對AOP的支持侷限於方法攔截。

引入了簡單的聲明式AOP與基於註解的AOP以後,Spring經典的看起來就顯得很是笨拙和過於複雜話,直接使用ProxyFactory bean 會讓人感受厭煩。

藉助於Spring的aop命名空間,咱們能夠將純POJO轉爲切面。

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

Spring所建立的通知都是用標準的Java類編寫的,定義通知所應用的切點一般會使用註解或在Spring配置文件裏採用XML來編寫

通知帶代理類中包裹切面,Spring在運行時把切面織入到Spring所管理的bean中。代理類封裝了目標類,並攔截被通知方法的調用。再把調用轉發給真正的目標bean。當代理攔截到方法調用時,在調用目標bean方法以前,會執行切面邏輯。直到應用須要被代理bean時,Spring纔會建立代理對象。若是使用ApplicationContext的話,在ApplicationContext從BeanFactory中加載全部的bean的時候,Spring纔會建立被代理的對象。由於Spirng運行時才建立代理對象,因此咱們不須要特殊的編譯器來織入Spring AOP的切面。

Spring基於動態代理,因此Spring只支持方法鏈接點。方便攔截能夠知足大部分的需求。

4.2 經過切點來選擇鏈接點

切點用於準肯定位應該在什麼地方應用切面的通知。通知和切點是切面最基本的元素。

Spring僅支持AspectJ切點指示器的一個子集。Spring是基於代理的,而某些切點表達式是基於代理的AOP無關的。

Spring支持的指示器,只有execution指示器是實際執行匹配的,而其餘的指示器都是用來限制匹配的。這說明execution指示器是咱們在編寫切點定義時最主要的指示器。

4.2.1編寫切點

爲了闡述Spring中的切面, 咱們須要有個主題來定義切面的切點。

package com.guo.cocert;
public interface Performance {
  public void perform();
}
execution(* concert.Performance.perform(..))

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

如今假設咱們須要配置的切點僅匹配concert包,可使用within()指示器

execution(* concert.Performance.perform(..)) && within(concert.*)

由於「&」在XMl中有特殊的含義,因此在Spring和XML配置中,描述切點時,可使用and代替「&&」。

4.2.2 在切點中選擇bean

Spring引入了一個新的bean()指示器,它容許咱們在切點表達式中使用bean的ID來標識bean。bean()使用bean ID 或 bean 名稱做爲參數來限制切點只匹配特定的bean。

execution(* concert.Performance.perform(..)) and bean("woodsotck")

也能夠這樣

execution(* concert.Performance.perform(..)) and !bean("woodsotck")

切面的通知會被編織到全部ID不爲woodsotck的bean中。

4.3使用註解建立切面

使用註解來建立切面是AspectJ 5所引入的關鍵特性。

4.3.1 定義切面

若是一場演出沒有觀衆的話,那不能稱之爲演出。

@AspectJ
public class Audience {

}

Audience類使用@AspectJ註解進行了標註。該註解代表Audience不只僅是一個POJO,仍是一個切面。Audience類中的方法都是使用註解來定義切面的具體行爲。

@AspectJ
public class Audience {
  @Pointcut("execution(* * concern.Performance.perform(..))")
  public void performance() {};
}

在Autience中,performance()方法使用了@Pointcut註解。爲@Pointcut註解設置的值是一個切點表達式,就像以前在通知註解上所設置的那樣。

須要注意的是,除了註解和沒有實際操做的performa()方法,Audience類依然是一個POJO,咱們可以像使用其餘的Java類那樣調用它的方法,它的方法也能獨立的進行單元測試。與其餘Java類沒有什麼區別。

像其餘的Java類同樣,它能夠裝配爲Spring中的bean

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

若是你就此止步的話,Audience只會是Spring容器中的一個bean。即使使用了AspectJ註解,但它並不會被視爲切面,這些註解不會解析,也不會建立將其轉化爲切面的代理。

若是你使用JavaConfig的話,能夠在配置類的級別上經過使用EnableAspectJ-AutoProxy註解啓用自動代理功能。

@Configuration
@EnableAspectJAutoProxy             //啓用AspectJ自動代理
@ComponentScan
public class ConcertConfig {
  @Bean
  public Audience autidence() {     //聲明Audience bean
    return new Audience();
  }
}

假如你在Spring中使用XMl來裝配bean的話,那麼須要使用Spring aop命名空間中的<aop:aspect-autoproxy>元素

<?xml version="1.0" encoding="UTF-8"?>

、、、、、、、、、、、、、、、、、、、、、、、、

<context:component-scan base-package="com.guo.concert"/>
<aop:aspect-autoproxy/>
<bean class="com.guo.concert.Audience"/>

無論你使用JavaConfig仍是XML,AspecJ自動代理都會使用@Aspect註解的bean建立一個代理。這個代理會圍繞着全部該切面的切點所匹配的bean。

咱們須要記住的是,Spring的AspectJ自動代理僅僅使用@AspectJ做爲建立切面的指導,切面依然是基於代理的。本質上它依然是Spring基於代理的切面。

4.3.2 建立環繞通知

環繞通知是最爲強大的通知類型,它可以讓你編寫的邏輯將被通知的目標方法安全包裝起來,實際上就像在一個通知方法中同時編寫前置通知和後置通知。

@AspectJ
public class Audience {
  @Pointcut("execution(* * concern.Performance.perform(..))")
  public void performance() {};
  @Around
  public void xx(Xxx jp) {
    .......
    jp.proced()
  }
}

在這裏,@Around註解,代表這個xx()方法會做爲performance()切點的環繞通知。

這個通知所達到的效果與以前的前置通知和後置通知是同樣的。

須要注意的是,別忘記調用proceed()方法,若是不調用這個方法,那麼你的通知實際上會阻塞對被通知方法的調用,有意思的是,你能夠不調用proceed方法,從而阻塞對被通知方法的反問,

4.3.4 經過註解引入新功能

一些編程語言,例如:Ruby和Groovy,有開放來的理念,它們能夠不直接使用修改對象或類的定義就可以爲對象或類增長新的方法。不過Java並非動態語言,一旦編譯完成了,就很難在爲該類添加新的功能了。

若是切面可以爲現有的方法增長額外的功能,爲何不恩那個爲一個對象增長新的方法呢?利用引入AOP的概念,切面能夠爲Spring bean 添加新的方法。

在Spring中,註解和自動代理提供了一種便利的方式來建立切面,它很是簡單,而且只設計最少的Spring配置,可是,面向註解的切面有一個明顯的不足點:你必須可以爲通知類添加註解,爲了作到這一點,必需要有源碼。

4.4 在XML中聲明切面

以前,有這樣一條原則:那就是基於註解的配置要優於Java的配置,基於Java的配置要優於XMl的配置,可是,若是你須要聲明切面,可是又不能爲通知類添加註解的時候 ,那麼就必須轉向XML配置了。

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

  • <aop:advisor> :定義AOP通知器
  • <aop:after> :定義AOP後置通知
  • <aop:after-returning> :定義AOP返回通知
  • <aop:after-throwing> :定義AOP異常通知
  • <aop:around> :定義AOP環繞通知
  • <aop:aspect> :定義一個切面
  • <aop:aspectj-autoproxy> :啓用@AspectJ註解
  • <aop:before> :定義一個AOP前置通知
  • <aop:poiontcut> :定義一個切點

4.4.1 聲明前置通知和後置通知

咱們會使用Spring aop命名空間中的一些元素,將沒有註解的Aurience類轉爲切面

<aop:config>
    <aop:aspect ref="audience">       <!--引用audience Bean-->

        <aop:before pointcut="execution(* * concert.Performance.perform(..))" method="silenceCellIphones"/>

        <aop:before pointcut="execution(* * concert.Performance.perform(..))" method="takeSeats"/>

        <aop:after-returning pointcut="execution(* * concert.Performance.perform(..))" method="applause"/>

        <aop:after-throwing pointcut="execution(* * concert.Performance.perform(..))" method="demandRefund"/>

    </aop:aspect>
</aop:config>

第一須要注意的就是大多數AOP配置元素必須在<aop:config>元素的上下文中使用。

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

在基於Aspectj註解的通知中,當發如今這些類型的重複時,使用@Pointcut註解來消除這些重複的內容。

以下的XMl配置展現瞭如何將通用的切點表達式抽取到一個切點聲明中,這樣,這個聲明就能在全部的通知元素中使用了

<aop:config>
    <aop:aspect ref="audience">       <!--引用audience Bean-->
        <aop:pointcut id="performance" expression="execution(* * concert.Performance.perform(..))"  />

        <aop:before pointcut="" method="silenceCellIphones"/>

        <aop:before pointcut-ref="performance" method="takeSeats"/>

        <aop:after-returning pointcut-ref="performance" method="applause"/>

        <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>

    </aop:aspect>
</aop:config>

如今的切點是一個地方定義的,而且被多個通知元素所引用,<aop:pointcut>元素定義了一個id爲performance的切點,同時修改全部的通知元素,用pointcut0ref來引用這個命名切點。

4.4.2 聲明環繞通知

相比於前置通知和後置通知,環繞通知在這點上有明顯的優點。使用環繞通知,咱們能夠完成前置通知和後置通知所實現的相同功能,並且只須要在一個方法中實現。由於整個通知邏輯都是在一個方法中實現的。

<aop:config>
    <aop:aspect ref="audience">       <!--引用audience Bean-->
        <aop:pointcut id="performance" expression="execution(* * concert.Performance.perform(..))"  />

        <aop:around pointcut-ref="performance" method="watchPerformance"/>

    </aop:aspect>
</aop:config>

像其餘通知的XML元素同樣,<aop:around>指定了一個切點和一個通知方法的名字。

4.4.3 爲通知傳遞參數

區別在於切點表達式中包含了一個參數,這個參數傳遞到通知方法中。還有區別就是這裏使用了and關鍵字

4.4.4 經過切面引入新的功能

藉助於AspectJ的@DeclareParents註解爲被通知的方法引入新的方法。可是AOP引入並非Aspectj特有的。使用Spring aop命名空間中的<aop:declare-parents>元素,咱們能夠實現相同的功能

<aop:config>
     <aop:aspect ref="audience">       <!--引用audience Bean-->
        <aop:declare-parents types-matching="concert.Performance"
                             implement-interface="concert.Encoreable"
                             default-impl="concert.DefaoultEncoreable"

     </aop:aspect>
 </aop:config>

4.5 注入AspectJ切面

雖然Spring AOP可以知足許多應用的切面需求,可是與AspectJ相比,Spring AOP是一個功能比較弱的AOP解決方案,ASpect提供了Spring AOP 所不能支持的許多類型的切點。

Spring不能像以前那樣使用<bean>聲明來建立一個實例----它已經在運行時由AspectJ建立完成了,Spring須要經過工廠方法獲取切面的引用。而後像<bean>元素規定的那樣在該對象上執行依賴注入

4.6 小節(重點中的重點)

AOP是面向對象編程的一個強大補充,經過AspectJ,咱們如今能夠把以前分散在應用各處的行爲放入可重用的模塊中。咱們顯示地聲明在何處如何應用該行爲。這樣有效減小了代碼冗餘,並讓咱們的類關注自身的主要功能。

Spring提供了一個AOP框架,讓咱們把切面插入到方法執行的周圍。如今咱們已經學會了如何把通知織入前置,後置和環繞方法的調用中,以及爲處理異常增長自定義行爲。

關於在Spirng應用中如何使用切面 ,咱們能夠有多種選擇。經過使用@AspectJ註解和簡化的配置命名空間,在Spring中裝配通知和切點變得很是簡單

最後,當Spring不能知足需求時,咱們必須轉向更爲強大的AspectJ。對於這些場景,咱們瞭解瞭如何使用Spring爲AspectJ切面注入依賴。

此時此刻,咱們已經覆蓋了Spring框架的基礎知識,瞭解到如何配置Spring容器以及如何爲Spring管理的對象應用切面,這些技術爲建立高內聚,低耦合的應用奠基了堅實的基礎。

從下一章開始,首先看到的是如何使用Spring構建Web應用。。

期待......

相關文章
相關標籤/搜索