天天看點

Spring AOP 掃盲

關于AOP

面向切面程式設計(Aspect-oriented Programming,俗稱AOP)提供了一種面向對象程式設計(Object-oriented Programming,俗稱OOP)的補充,面向對象程式設計最核心的單元是類(class),然而面向切面程式設計最核心的單元是切面(Aspects)。與面向對象的順序流程不同,AOP采用的是橫向切面的方式,注入與主業務流程無關的功能,例如事務管理和日志管理。

Spring AOP 掃盲

Spring的一個關鍵元件是AOP架構。 雖然Spring IoC容器不依賴于AOP(意味着你不需要在IOC中依賴AOP),但AOP為Spring IoC提供了非常強大的中間件解決方案。

AOP 是一種程式設計範式,最早由 AOP 聯盟的組織提出的,通過預編譯方式和運作期動态代理實作程式功能的統一維護的一種技術。它是 OOP的延續。利用 AOP 可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率

我們之間的開發流程都是使用順序流程,那麼使用 AOP 之後,你就可以橫向抽取重複代碼,什麼叫橫向抽取呢?或許下面這幅圖你能了解,先來看一下傳統的軟體開發存在什麼樣風險。

縱向繼承體系:

Spring AOP 掃盲

在改進方案之前,我們或許都遇到過 IDEA 對你輸出 Duplicate Code 的時候,這個時候的類的設計是很糟糕的,代碼寫的也很備援,基本上 if...else... 完成所有事情,這個時候就需要把相同的代碼抽取出來成為公共的方法,降低耦合性。這種提取代碼的方式是縱向抽取,縱向抽取的代碼之間的關聯關系非常密切。

橫向抽取也是代碼提取的一種方式,不過這種方式不會修改主要業務邏輯代碼,隻是在此基礎上添加一些與主要的業務邏輯無關的功能,AOP 采取橫向抽取機制,補充了傳統縱向繼承體系(OOP)無法解決的重複性 代碼優化(性能監視、事務管理、安全檢查、緩存),将業務邏輯和系統處理的代碼(關閉連接配接、事務管理、記錄檔記錄)解耦。

AOP 的概念

在深入學習SpringAOP 之前,讓我們先對AOP的幾個基本術語有個大緻的概念,這些概念不是很容易了解,比較抽象,可以知道有這麼幾個概念,下面一起來看一下:

  • 切面(Aspect)

    : Aspect 聲明類似于 Java 中的類聲明,事務管理是AOP一個最典型的應用。在AOP中,切面一般使用

    @Aspect

    注解來使用,在XML 中,可以使用

    <aop:aspect>

    來定義一個切面。
  • 連接配接點(Join Point)

    : 一個在程式執行期間的某一個操作,就像是執行一個方法或者處理一個異常。在Spring AOP中,一個連接配接點就代表了一個方法的執行。
  • 通知(Advice):

    在切面中(類)的某個連接配接點(方法出)采取的動作,會有四種不同的通知方式: around(環繞通知),before(前置通知),after(後置通知), exception(異常通知),return(傳回通知)。許多AOP架構(包括Spring)将建議把通知作為為攔截器,并在連接配接點周圍維護一系列攔截器。
  • 切入點(Pointcut):

    表示一組連接配接點,通知與切入點表達式有關,并在切入點比對的任何連接配接點處運作(例如執行具有特定名稱的方法)。由切入點表達式比對的連接配接點的概念是AOP的核心,Spring預設使用AspectJ切入點表達式語言。
  • 介紹(Introduction):

    introduction可以為原有的對象增加新的屬性和方法。例如,你可以使用introduction使bean實作IsModified接口,以簡化緩存。
  • 目标對象(Target Object):

    由一個或者多個切面代理的對象。也被稱為"切面對象"。由于Spring AOP是使用運作時代理實作的,是以該對象始終是代理對象。
  • AOP代理(AOP proxy):

    由AOP架構建立的對象,在Spring架構中,AOP代理對象有兩種:JDK動态代理和CGLIB代理
  • 織入(Weaving):

    是指把增強應用到目标對象來建立新的代理對象的過程,它(例如 AspectJ 編譯器)可以在編譯時期,加載時期或者運作時期完成。與其他純Java AOP架構一樣,Spring AOP在運作時進行織入。

Spring AOP 中通知的分類

  • 前置通知(Before Advice): 在目标方法被調用前調用通知功能;相關的類

    org.springframework.aop.MethodBeforeAdvice

  • 後置通知(After Advice): 在目标方法被調用之後調用通知功能;相關的類

    org.springframework.aop.AfterReturningAdvice

  • 傳回通知(After-returning): 在目标方法成功執行之後調用通知功能;
  • 異常通知(After-throwing): 在目标方法抛出異常之後調用通知功能;相關的類

    org.springframework.aop.ThrowsAdvice

  • 環繞通知(Around): 把整個目标方法包裹起來,在被調用前和調用之後分别調用通知功能相關的類

    org.aopalliance.intercept.MethodInterceptor

Spring AOP 中織入的三種時期

  • 編譯期:

    切面在目标類編譯時被織入,這種方式需要特殊的編譯器。AspectJ 的織入編譯器就是以這種方式織入切面的。
  • 類加載期:

    切面在目标類加載到 JVM 時被織入,這種方式需要特殊的類加載器( ClassLoader ),它可以在目标類引入應用之前增強目标類的位元組碼。
  • 運作期:

    切面在應用運作的某個時期被織入。一般情況下,在織入切面時,AOP容器會為目标對象動态建立一個代理對象,Spring AOP 采用的就是這種織入方式。

AOP 的兩種實作方式

AOP 采用了兩種實作方式:靜态織入(AspectJ 實作)和動态代理(Spring AOP實作)

AspectJ

AspectJ 是一個采用Java 實作的AOP架構,它能夠對代碼進行編譯(一般在編譯期進行),讓代碼具有AspectJ 的 AOP 功能,AspectJ 是目前實作 AOP 架構中最成熟,功能最豐富的語言。ApectJ 主要采用的是編譯期靜态織入的方式。在這個期間使用 AspectJ 的 acj 編譯器(類似 javac)把 aspect 類編譯成 class 位元組碼後,在 java 目标類編譯時織入,即先編譯 aspect 類再編譯目标類。

Spring AOP 掃盲

Spring AOP 實作

Spring AOP 是通過動态代理技術實作的,而動态代理是基于反射設計的。Spring AOP 采用了兩種混合的實作方式:JDK 動态代理和 CGLib 動态代理,分别來了解一下

Spring AOP 掃盲
  • JDK動态代理:Spring AOP的首選方法。 每當目标對象實作一個接口時,就會使用JDK動态代理。目标對象必須實作接口
  • CGLIB代理:如果目标對象沒有實作接口,則可以使用CGLIB代理。

Spring 對 AOP的支援

Spring 提供了兩種AOP 的實作:基于注解式配置和基于XML配置

@AspectJ 支援

為了在Spring 配置中使用

@AspectJ

,你需要啟用Spring支援,以根據@AspectJ切面配置Spring AOP,并配置自動代理。自動代理意味着,Spring 會根據自動代理為 Bean 生成代理來攔截方法的調用,并確定根據需要執行攔截。

可以使用XML或Java樣式配置啟用@AspectJ支援。 在任何一種情況下,都還需要確定AspectJ的

aspectjweaver.jar

第三方庫位于應用程式的類路徑中(版本1.8或更高版本)。

開啟@AspectJ 支援

使用

@Configuration

支援@AspectJ 的時候,需要添加

@EnableAspectJAutoProxy

注解,就像下面例子展示的這樣來開啟 AOP代理

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}
           

也可以使用XML配置來開啟@AspectJ 支援

<aop:aspectj-autoproxy/>
           

預設你已經添加了 aop 的schema 空間,如果沒有的話,你需要手動添加

<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->
</beans>
           

聲明一個切面

在啟用了@AspectJ支援的情況下,在應用程式上下文中定義的任何bean都具有@AspectJ方面的類(具有@Aspect注釋),Spring會自動檢測并用于配置Spring AOP。

使用XML 配置的方式定義一個切面

<aop:aspect />
           

使用注解的方式定義一個切面

@Aspect
public class MyAspect {}
           

切面(也就是用@Aspect注解的類)就像其他類一樣有屬性和方法。它們能夠包含切入點,通知和介紹聲明。

通過自動掃描檢測切面

你可以在Spring XML 配置中将切面類注冊為正常的bean,或者通過類路徑掃描自動檢測它們 - 與任何其他Spring管理的bean相同。然而,隻是注解了@Aspect 的類不會被當作bean 進行管理,你還需要在類上面添加 @Component 注解,把它當作一個元件交給 Spring 管理。

定義一個切點

一個切點由兩部分組成:包含名稱和任何參數以及切入點表達式的簽名,該表達式能夠确定我們想要執行的方法。在@AspectJ注釋風格的AOP中,切入點表達式需要用

@Pointcut

注解标注(這個表達式作為方法的簽名,它的傳回值必須是 void)。

@Pointcut("execution(* transfer(..))") // 切入點表達式
private void definePointcut() {}// 方法簽名
           

切入點表達式的編寫規則如下:

Spring AOP 掃盲

現在假設我們需要配置的切點僅僅比對指定的包,就可以使用

within()

限定符來表示,如下表達式所述:

Spring AOP 掃盲

請注意我們使用了

&&

操作符把 execution() 和 within() 訓示器連接配接在一起,表示的是 和 的關系,類似的,你還可以使用

||

操作來表示 或 的關系, 使用

!

表示 非 的關系。

除了within() 表示的限定符外,還有其它的限定符,下面是一個限定符表

AspectJ 描述符 描述
arg() 限制連接配接點比對參數為指定類型的執行方法
@args() 限制連接配接點比對參數由指定注解标注的執行方法
execution() 用于比對是連接配接點的執行方法
this() 限制連接配接點比對的AOP代理的bean引用為指定類型的類
target 限制連接配接點比對目标對象為指定類型的類
@target() 限制連接配接點比對特定的執行對象,這些對象對應的類要具有指定類型的注解
within() 限制連接配接點比對指定的類型
@within() 限制連接配接點比對指定注解所标注的類型
@annotationn 限定比對帶有指定注解的連接配接點

使用XML配置來配置切點

<aop:config>
	<aop:aspect ref = "">
  	<aop:poincut id = "" expression="execution(** com.cxuan.aop.definePointcut(......))"/>
  </aop:aspect>
</aop:config>
           

聲明一個通知

通知是和切入點表達式互相關聯,用于在方法執行之前,之後或者方法前後,方法傳回,方法抛出異常時調用通知的方法,切入點表達式可以是對命名切入點的簡單引用,也可以是在适當位置聲明的切入點表達式。下面以一個例子來示範一下這些通知都是如何定義的:

Spring AOP 掃盲

上面的例子就很清晰了,定義了一個

Audience

切面,并在切面中定義了一個

performance()

的切點,下面各自定義了表演之前、表演之後傳回、表演失敗的時候進行通知,除此之外,你還需要在main 方法中開啟

@EnableAspectJAutoProxy

來開啟自動代理。

除了使用Java Config 的方式外,你還可以使用基于XML的配置方式

Spring AOP 掃盲

當然,這種切點定義的比較備援,為了解決這種類似

if...else...

災難性的業務邏輯,你需要單獨定義一個

<aop:pointcut>

,然後使用

pointcut-ref

屬性指向上面那個标簽,就像下面這樣

Spring AOP 掃盲

環繞通知

在目标方法執行之前和之後都可以執行額外代碼的通知。在環繞通知中必須顯式的調用目标方法,目标方法才會執行,這個顯式調用時通過

ProceedingJoinPoint

來實作的,可以在環繞通知中接收一個此類型的形參,spring容器會自動将該對象傳入,注意這個參數必須處在環繞通知的第一個形參位置。

環繞通知需要傳回傳回值,否則真正調用者将拿不到傳回值,隻能得到一個null。下面是環繞通知的一個示例

<aop:around method="around" pointcut-ref="pc1"/>
           
public Object around(ProceedingJoinPoint jp) throws Throwable{
   System.out.println("1 -- around before...");
   Object obj = jp.proceed(); //--顯式的調用目标方法
   System.out.println("1 -- around after...");
   return obj;
 }
           

文章參考:

https://juejin.im/post/5a695b3cf265da3e47449471

《Spring In Action》

https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/core.html

Spring AOP 五大通知類型