1. 程式人生 > 程式設計 >Spring AOP 詳解

Spring AOP 詳解

情景案例

小明辛苦忙了一整年終於完成了包含300個介面的業務系統專案。專案圓滿上線並穩定運行了一段時間了。突然有一天總監說,對於會造成資料變化的所有介面,我們必須記錄使用者的操作日誌。然後小明就吭哧吭哧給其中150個介面,挨個加上日誌程式碼,累得真夠嗆。

過了一陣子總監又說,所有變化很少的資料全部都加上快取,快取涉及到重新整理快取、獲取快取、刪除快取的問題。於是乎,小明就又吭哧吭哧地給其中的100個介面加上快取相關的程式碼。

又過了一陣子總監說,所有涉及充值退款費用相關的介面,需要生成發票單存入資料庫。這時候小明又需要吭哧吭哧給涉及到的50個介面,挨個加上發票儲存操作。

小明天天加班也沒在工期內完成任務,並且原本的業務程式碼已經變得臃腫不堪了。

原本的程式碼:

/**
 * 業務方法
 */
public static void method() {

    // 業務操作
    doBusiness();

}
複製程式碼

經過硬編碼新增各種非業務性程式碼後的業務程式碼:

/**
 * 業務方法
 */
public static void method() {
    // 日誌操作
    doLog();
    // 業務操作
    doBusiness();
    // 快取操作
    doLog();
    // 發票操作
    doReceipt();

}
複製程式碼

讀者應該能明顯感受到在沒有AOP代理的情況下的缺點

  1. 業務程式碼和非業務程式碼混雜在一起,原本清晰的業務流程淹沒在與業務不相關的程式碼中。
  2. 增加非業務性的功能時,都需要手工硬編碼去實現,費時費力。
  3. 程式碼變得不好維護,一是程式碼耦合度高,二是需要通過硬編碼的方式去拓展或者修改功能。

AOP是什麼?

在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。主要目標還是致力於解耦,我們可以看到解耦這一理念貫穿於我們的整個編碼工作中。我們通過各種設計模式或者設計原則來使得物件之間解耦。通過Spring IOC容器中利用依賴注入使得物件之間的耦合度更低。而AOP的思想解耦得更徹底,通過動態的新增功能來增強實現,並且做到毫無程式碼的侵入性。利用AOP可以對業務邏輯和非業務邏輯的部分進行隔離,可以提取非業務邏輯的部分,提高程式的可重用性,同時提高了開發的效率。

如何理解“切面”二字呢?

15703790331.png

我們的業務流程方法都是自頂向下垂直的,而當我們需要給這些業務方法統一加上某些非業務功能的話,就會發現這些非業務功能方法在圖上會連成一條直線,並與原來的業務流程方法垂直橫切。

為什麼使用AOP?

  1. 核心業務程式碼與切面程式碼解耦,切面程式碼對核心業務程式碼完全無侵入,遵守單一職責原則,完全隔離核心業務程式碼與切面程式碼。

  2. 低耦合帶來可維護性高,修改或者新增一個切面程式碼僅需集中在一處進行更改。低耦合也意味著切面程式碼可複用性高。

  3. Spring IOC容器天然地為AOP的實現提供了便利,IOC和AOP的結合使得Spring的解耦能力更強。

AOP例子

先宣告切面類:

/**
 *  註解@Aspect標識該類為切面類
 */
@Component
@Aspect
public class PersonAspect {

    // 通過表示式定義切入點
    @Pointcut("execution(* com.valarchie.aop.MeetingServiceImpl.meeting(..))")
    public void conference() {}
 
    // 前置通知
    @Before("meeting()")
    public void takeSeats() {
        System.out.println("開會前,找到位置坐");
    }
 
    // 前置通知
    @Before("meeting()")
    public void silenceCellPhones() {
        System.out.println("開會前,手機調成靜音");
    }
 
    // 後置通知
    @After("meeting()")
    public void summary() {
        System.out.println("開會後,寫總結報告");
    }
 
}

複製程式碼

建立要被代理的介面,即MeetingService會議服務

public interface MeetingService {
    void meeting();
}
複製程式碼

建立MeetingService會議服務的具體實現

@Component
public class MeetingServiceImpl implements MeetingService {
 
    @Override
    public void meeting() {
        System.out.println("會議進行中..");
    }
 
}

複製程式碼

定義AOP配置

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan("com.valarchie")
public class AppConfig {
 
}
複製程式碼
  1. 註解@EnableAspectJAutoProxy開啟代理;
  2. 如果屬性proxyTargetClass預設為false,表示使用jdk動態代理織入增強;
  3. 如果屬性proxyTargetClass設定為true,表示使用Cglib動態代理技術織入增強;
  4. 如果屬性proxyTargetClass設定為false,但是目標類沒有宣告介面, Spring aop還是會使用Cglib動態代理,也就是說非介面的類要生成代理都用Cglib。

測試AOP:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class AopTest {
 
    @Autowired
    private MeetingServiceImpl meetingService;
 
    @Test
    public void testAopAnnotation() {
        meetingService.meeting();
    }
 
}

複製程式碼

執行結果:

開會前,手機調成靜音
開會前,找到位置坐
會議進行中..
開會後,寫總結報告
複製程式碼

在這個AOP的例子當中我們沒有在會議服務實現類當中硬編碼需要新增的切面功能,而是通過另外新建一個類來描述切面,以及需要在切面上增強的功能。這樣的實現是不是更優雅呢?

關於切入點的表示式稍微解析一下:

例如定義切入點表示式  execution (* com.sample.service.impl..*.*(..))

execution()是最常用的切點函式,其語法如下所示:

 整個表示式可以分為五個部分:

 1、execution(): 表示式主體。

 2、第一個*號:表示返回型別,*號表示所有的型別。

 3、包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,
 com.sample.service.impl包、子孫包下所有類的方法。

 4、第二個*號:表示類名,*號表示所有的類。

 5、*(..):最後這個星號表示方法名,*號表示所有的方法,後面括弧裡面表示方法
 的引數,兩個句點表示任何引數。
複製程式碼

以上的例子當中涉及不少AOP概念,接下來我們針對這些概念進行逐一解釋。

AOP中的概念闡述

  • 連線點(Joinpoint) 程式執行的某個特定位置:如類開始初始化前、類初始化後、類某個方法呼叫前、呼叫後、方法丟擲異常後。一個類或一段程式程式碼擁有一些具有邊界性質的特定點,這些點中的特定點就稱為“連線點”。Spring僅支援方法的連線點,即僅能在方法呼叫前、方法呼叫後、方法丟擲異常時以及方法呼叫前後這些程式執行點織入增強。連線點由兩個資訊確定:第一是用方法表示的程式執行點;第二是用相對點表示的方位。

  • 切點(Pointcut) 每個程式類都擁有多個連線點,如一個擁有兩個方法的類,這兩個方法都是連線點,即連線點是程式類中客觀存在的事物。AOP通過“切點”定位特定的連線點。連線點相當於資料庫中的記錄,而切點相當於查詢條件。切點和連線點不是一對一的關係,一個切點可以匹配多個連線點。在Spring中,切點通過org.springframework.aop.Pointcut介面進行描述,它使用類和方法作為連線點的查詢條件,Spring AOP的規則解析引擎負責切點所設定的查詢條件,找到對應的連線點。其實確切地說,不能稱之為查詢連線點,因為連線點是方法執行前、執行後等包括方位資訊的具體程式執行點,而切點只定位到某個方法上,所以如果希望定位到具體連線點上,還需要提供方位資訊。

  • 增強(Advice) 增強是織入到目標類連線點上的一段程式程式碼,在Spring中,增強除用於描述一段程式程式碼外,還擁有另一個和連線點相關的資訊,這便是執行點的方位。結合執行點方位資訊和切點資訊,我們就可以找到特定的連線點。

  • 目標物件(Target) 增強邏輯的織入目標類。如果沒有AOP,目標業務類需要自己實現所有邏輯,而在AOP的幫助下,目標業務類只實現那些非橫切邏輯的程式邏輯,而效能監視和事務管理等這些橫切邏輯則可以使用AOP動態織入到特定的連線點上。

  • 引介(Introduction) 引介是一種特殊的增強,它為類新增一些屬性和方法。這樣,即使一個業務類原本沒有實現某個介面,通過AOP的引介功能,我們可以動態地為該業務類新增介面的實現邏輯,讓業務類成為這個介面的實現類。

  • 織入(Weaving) 織入是將增強新增對目標類具體連線點上的過程。AOP像一臺織布機,將目標類、增強或引介通過AOP這臺織布機天衣無縫地編織到一起。根據不同的實現技術,AOP有三種織入的方式: a、編譯期織入,這要求使用特殊的Java編譯器。 b、類裝載期織入,這要求使用特殊的類裝載器。 c、動態代理織入,在執行期為目標類新增增強生成子類的方式。 Spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。

  • 代理(Proxy) 一個類被AOP織入增強後,就產出了一個結果類,它是融合了原類和增強邏輯的代理類。根據不同的代理方式,代理類既可能是和原類具有相同介面的類,也可能就是原類的子類,所以我們可以採用呼叫原類相同的方式呼叫代理類。

  • 切面(Aspect) 切面由切點和增強(引介)組成,它既包括了橫切邏輯的定義,也包括了連線點的定義,Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯織入到切面所指定的連線點中。

筆者個人理解,如有錯誤懇請網友評論指正。

轉自我的個人部落格 vc2x.com