Spring AOP就是這麼簡單啦

前言

只有光頭才能變強

上一篇已經講解了Spring IOC知識點一網打盡!,這篇主要是講解Spring的AOP模塊~java

以前我已經寫過一篇關於AOP的文章了,那篇把比較重要的知識點都講解過了一篇啦:Spring【AOP模塊】就這麼簡單,很榮幸被開源中國推薦過~~spring

  • 若是沒有AOP的基礎,建議先看看上面那篇文章~
  • 若是沒有代理模式基礎,建議先看看:給女友講解什麼是代理模式這篇文章
  • 若是都看過了,這篇就放心食用吧!

這篇文章主要是補充和強化一些比較重要的知識點,並會把上面的兩本書關於AOP的知識點整理出來並畫成一個思惟導圖來全面瞭解Spring AOP的知識點!編程

那麼接下來就開始吧,若是有錯的地方但願能多多包涵,並不吝在評論區指正!微信

1、Spring AOP全面認知

結合《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》兩本書的AOP章節將其知識點整理起來~編輯器

1.1AOP概述

AOP稱爲面向切面編程,那咱們怎麼理解面向切面編程??post

咱們能夠先看看下面這段代碼:性能

咱們學Java面向對象的時候,若是代碼重複了怎麼辦啊??能夠分紅下面幾個步驟:測試

  • 1:抽取成方法
  • 2:抽取類

抽取成類的方式咱們稱之爲:縱向抽取spa

  • 經過繼承的方式實現縱向抽取

可是,咱們如今的辦法不行:即便抽取成類仍是會出現重複的代碼,由於這些邏輯(開始、結束、提交事務)依附在咱們業務類的方法邏輯中翻譯

如今縱向抽取的方式不行了,AOP的理念:就是將分散在各個業務邏輯代碼中相同的代碼經過橫向切割的方式抽取到一個獨立的模塊中!

上面的圖也很清晰了,將重複性的邏輯代碼橫切出來其實很容易(咱們簡單可認爲就是封裝成一個類就行了),但咱們要將這些被咱們橫切出來的邏輯代碼融合到業務邏輯中,來完成和以前(沒抽取前)同樣的功能!這就是AOP首要解決的問題了!

1.2Spring AOP原理

被咱們橫切出來的邏輯代碼融合到業務邏輯中,來完成和以前(沒抽取前)同樣的功能

沒有學Spring AOP以前,咱們就可使用代理來完成。

  • 若是看過我寫的給女友講解什麼是代理模式這篇文章的話,必定就不難理解上面我說的那句話了
  • 代理能幹嗎?代理能夠幫咱們加強對象的行爲!使用動態代理實質上就是調用時攔截對象方法,對方法進行改造、加強

其實Spring AOP的底層原理就是動態代理

來源《精通Spring4.x 企業應用開發實戰》一段話:

Spring AOP使用純Java實現,它不須要專門的編譯過程,也不須要特殊的類裝載器,它在 運行期經過代理方式向目標類織入加強代碼。在Spring中能夠無縫地將Spring AOP、IoC和AspectJ整合在一塊兒。

來源《Spring 實戰 (第4版)》一句話:

Spring AOP構建在動態代理基礎之上,所以, Spring對AOP的支持侷限於方法攔截

在Java中動態代理有兩種方式:

  • JDK動態代理
  • CGLib動態代理

JDK動態代理是須要實現某個接口了,而咱們類未必所有會有接口,因而CGLib代理就有了~~

  • CGLib代理其生成的動態代理對象是目標類的子類
  • Spring AOP默認是使用JDK動態代理,若是代理的類沒有接口則會使用CGLib代理

那麼JDK代理和CGLib代理咱們該用哪一個呢??在《精通Spring4.x 企業應用開發實戰》給出了建議:

  • 若是是單例的咱們最好使用CGLib代理,若是是多例的咱們最好使用JDK代理

緣由:

  • JDK在建立代理對象時的性能要高於CGLib代理,而生成代理對象的運行性能卻比CGLib的低。
  • 若是是單例的代理,推薦使用CGLib

看到這裏咱們就應該知道什麼是Spring AOP(面向切面編程)了:將相同邏輯的重複代碼橫向抽取出來,使用動態代理技術將這些重複代碼織入到目標對象方法中,實現和原來同樣的功能

  • 這樣一來,咱們就在寫業務時只關心業務代碼,而不用關心與業務無關的代碼

1.3AOP的實現者

AOP除了有Spring AOP實現外,還有著名的AOP實現者:AspectJ,也有可能你們沒據說過的實現者:JBoss AOP~~

咱們下面來講說AspectJ擴展一下知識面:

AspectJ是 語言級別的AOP實現,擴展了Java語言,定義了AOP語法,可以在 編譯期提供橫切代碼的織入,因此它有 專門的編譯器用來生成遵照Java字節碼規範的Class文件。

而Spring借鑑了AspectJ不少很是有用的作法,融合了AspectJ實現AOP的功能。但Spring AOP本質上底層仍是動態代理,因此Spring AOP是不須要有專門的編輯器的~

1.4AOP的術語

嗯,AOP搞了好幾個術語出來~~兩本書都有講解這些術語,我會盡可能讓你們看得明白的:

鏈接點(Join point):

  • 可以被攔截的地方:Spring AOP是基於動態代理的,因此是方法攔截的。每一個成員方法均可以稱之爲鏈接點~

切點(Poincut):

  • 具體定位的鏈接點:上面也說了,每一個方法均可以稱之爲鏈接點,咱們具體定位到某一個方法就成爲切點

加強/通知(Advice):

  • 表示添加到切點的一段邏輯代碼,並定位鏈接點的方位信息

    • 簡單來講就定義了是幹什麼的,具體是在哪幹
    • Spring AOP提供了5種Advice類型給咱們:前置、後置、返回、異常、環繞給咱們使用!

織入(Weaving):

  • 加強/通知添加到目標類的具體鏈接點上的過程。

引入/引介(Introduction):

  • 引入/引介容許咱們向現有的類添加新方法或屬性。是一種特殊的加強!

切面(Aspect):

  • 切面由切點和加強/通知組成,它既包括了橫切邏輯的定義、也包括了鏈接點的定義。

在《Spring 實戰 (第4版)》給出的總結是這樣子的:

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

總的來講:

  • 這些術語可能翻譯過來不太好理解,但對咱們正常使用AOP的話影響並無那麼大~~看多了就知道它是什麼意思了。

1.5Spring對AOP的支持

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

  • 基於代理的經典SpringAOP

    • 須要實現接口,手動建立代理
  • 純POJO切面

    • 使用XML配置,aop命名空間
  • @AspectJ註解驅動的切面

    • 使用註解的方式,這是最簡潔和最方便的!

2、基於代理的經典SpringAOP

這部分配置比較麻煩,用起來也很麻煩,這裏我就主要整理一下書上的內容,你們看看了解一下吧,咱們實際上使用Spring AOP基本不用這種方式了!

首先,咱們來看一下加強接口的繼承關係圖:

能夠分紅五類加強的方式:

Spring提供了六種的切點類型

切面類型主要分紅了三種

  • 通常切面
  • 切點切面
  • 引介/引入切面

通常切面,切點切面,引介/引入切面介紹:


對於切點切面咱們通常都是直接用就行了,咱們來看看引介/引入切面是怎麼一回事:

  • 引介/引入切面是引介/引入加強的封裝器,經過引介/引入切面,能夠更容易地爲現有對象添加任何接口的實現

繼承關係圖:

引介/引入切面有兩個實現類:

  • DefaultIntroductionAdvisor:經常使用的實現類
  • DeclareParentsAdvisor:用於實現AspectJ語言的DeclareParent註解表示的引介/引入切面

實際上,咱們使用AOP每每是Spring內部使用BeanPostProcessor幫咱們建立代理

這些代理的建立器能夠分紅三類:

  • 基於Bean配置名規則的自動代理建立器:BeanNameAutoProxyCreator
  • 基於Advisor匹配機制的自動代理建立器:它會對容器全部的Advisor進行掃描,實現類爲DefaultAdvisorAutoProxyCreator
  • 基於Bean中的AspectJ註解標籤的自動代理建立器:AnnotationAwareAspectJAutoProxyCreator

對應的類繼承圖:

嗯,基於代理的經典SpringAOP就講到這裏吧,其實我是不太願意去寫這個的,由於已經幾乎不用了,在《Spring 實戰 第4版》也沒有這部分的知識點了。

  • 可是經過這部分的知識點能夠更加全面地認識Spring AOP的各類接口吧~

3、擁抱基於註解和命名空的AOP編程

Spring在新版本中對AOP功能進行了加強,體如今這麼幾個方面:

  • 在XML配置文件中爲AOP提供了aop命名空間
  • 增長了AspectJ切點表達式語言的支持
  • 能夠無縫地集成AspectJ

那咱們使用@AspectJ來玩AOP的話,學什麼??其實也就是上面的內容,學如何設置切點、建立切面、加強的內容是什麼...

具體的切點表達式使用仍是前往:Spring【AOP模塊】就這麼簡單看吧~~

對應的加強註解:

3.1使用引介/引入功能實現爲Bean引入新方法

其實前置啊、後置啊這些很容易就理解了,整篇文章看下來就只有這個引介/引入切面有點搞頭。因而咱們就來玩玩吧~

咱們來看一下具體的用法吧,如今我有個服務員的接口:

public interface Waiter {

    // 向客人打招呼
    void greetTo(String clientName);

    // 服務
    void serveTo(String clientName);
}

一位年輕服務員實現類:

public class NaiveWaiter implements Waiter {
    public void greetTo(String clientName) {
        System.out.println("NaiveWaiter:greet to " + clientName + "...");
    }

    @NeedTest
    public void serveTo(String clientName) {
        System.out.println("NaiveWaiter:serving " + clientName + "...");
    }

}

如今我想作的就是:想這個服務員能夠充當售貨員的角色,能夠賣東西!固然了,我確定不會加一個賣東西的方法到Waiter接口上啦,由於這個是暫時的~

因此,我搞了一個售貨員接口:

public interface Seller {

  // 賣東西
  int sell(String goods, String clientName);
}

一個售貨員實現類:

public class SmartSeller implements Seller {

    // 賣東西
    public int sell(String goods,String clientName) {
        System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
        return 100;
    }
    
}

此時,咱們的類圖是這樣子的:

如今我想幹的就是:藉助AOP的引入/引介切面,來讓咱們的服務員也能夠賣東西

咱們的引入/引介切面具體是這樣乾的:

@Aspect
public class EnableSellerAspect {
    
    @DeclareParents(value = "com.smart.NaiveWaiter",  // 指定服務員具體的實現
            defaultImpl = SmartSeller.class) // 售貨員具體的實現
    public Seller seller; // 要實現的目標接口
    
}

寫了這個切面類會發生什麼??

  • 切面技術將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實現了Seller接口!!!!

是否是很神奇??我也以爲很神奇啊,咱們來測試一下:

咱們的bean.xml文件很簡單:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <aop:aspectj-autoproxy/>
    <bean id="waiter" class="com.smart.NaiveWaiter"/>
    <bean class="com.smart.aspectj.basic.EnableSellerAspect"/>
</beans>

測試一下:

public class Test {
    public static void main(String[] args) {


        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/aspectj/basic/beans.xml");
        Waiter waiter = (Waiter) ctx.getBean("waiter");

        // 調用服務員原有的方法
        waiter.greetTo("Java3y");
        waiter.serveTo("Java3y");

        // 經過引介/引入切面已經將waiter服務員實現了Seller接口,因此能夠強制轉換
        Seller seller = (Seller) waiter;
        seller.sell("水軍", "Java3y");

    }
}

具體的調用過程是這樣子的:

當引入接口方法被調用時,代理對象會把此調用委託給實現了新接口的某個其餘對象。實際上,一個Bean的實現被拆分到多個類中

3.2在XML中聲明切面

咱們知道註解很方便,可是,要想使用註解的方式使用Spring AOP就必需要有源碼(由於咱們要在切面類上添加註解)。若是沒有源碼的話,咱們就得使用XML來聲明切面了~

其實就跟註解差很少的功能:

咱們就直接來個例子終結掉它吧:

首先咱們來測試一下與傳統的SpringAOP結合的advisor是怎麼用的:

實現類:

xml配置文件:

.......

一個一個來說解仍是太花時間了,我就一次性用圖的方式來說啦:

最後還有一個切面類型總結圖,看完就幾乎懂啦:

3、總結

看起來AOP有不少不少的知識點,其實咱們只要記住AOP的核心概念就行啦。

下面是個人簡要總結AOP:

  • AOP的底層其實是動態代理,動態代理分紅了JDK動態代理和CGLib動態代理。若是被代理對象沒有接口,那麼就使用的是CGLIB代理(也能夠直接配置使用CBLib代理)
  • 若是是單例的話,那咱們最好使用CGLib代理,由於CGLib代理對象運行速度要比JDK的代理對象要快
  • AOP既然是基於動態代理的,那麼它只能對方法進行攔截,它的層面上是方法級別的
  • 不管經典的方式、註解方式仍是XML配置方式使用Spring AOP的原理都是同樣的,只不過形式變了而已。通常咱們使用註解的方式使用AOP就行了。
  • 註解的方式使用Spring AOP就瞭解幾個切點表達式,幾個加強/通知的註解就完事了,是否是賊簡單...使用XML的方式和註解其實沒有很大的區別,很快就能夠上手啦。
  • 引介/引入切面也算是一個比較亮的地方,能夠用代理的方式爲某個對象實現接口,從而可以使用藉口下的方法。這種方式是非侵入式的~
  • 要加強的方法還能夠接收與被代理方法同樣的參數、綁定被代理方法的返回值這些功能...

最後,將咱們上一次IOC的思惟導圖補充AOP的知識點上去吧~~~

參考資料:

  • 《Spring 實戰》
  • 《精通Spring4.x 企業應用開發實戰》
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠 關注微信公衆號:Java3y。爲了你們方便,剛新建了一下 qq羣:742919422,你們也能夠去交流交流。謝謝支持了!但願能多介紹給其餘有須要的朋友

文章的目錄導航

相關文章
相關標籤/搜索