1. 程式人生 > >Spring 5 設計模式 - 使用代理和裝飾模式的Spring AOP

Spring 5 設計模式 - 使用代理和裝飾模式的Spring AOP

Spring 5 設計模式 - 使用代理和裝飾模式的Spring AOP

Spring中的代理模式

代理模式,就是向外界提供一個類,代表另一個類的功能。
Spring提供兩種代理

JDK proxy CGLIB proxy
也叫動態代理 不是JDK內建的
API在JDK內 在Spring JARs內
要實現介面 沒有介面時使用
可代理介面 不能用於final類和方法(因為不能被覆蓋)

both the proxies

什麼是AOP

Aspect-Oriented Programming (AOP)能夠模組化cross-cutting concerns。它是另一種程式設計正規化,補充了OOP。
OOP的關鍵要素是類和物件,AOP的關鍵要素則是aspect。Aspects允許你在程式的多個地方模組化一些功能(cross-cutting concerns)。
比如,安全是程式裡的一個cross-cutting concerns,因為我們不得不在程式的許多需要安全的方法中應用它。類似的,事務和日誌也是cross-cutting concerns。
cross-cutting concerns

AOP要解決的問題

如果不使用AOP,cross-cutting功能和業務邏輯就會混雜在一起。一般會導致兩個問題:程式碼糾纏和程式碼分散

程式碼糾纏

public class TransferServiceImpl implements TransferService {
    public void transfer(Account a, Account b, Double amount) {
        //Security concern start here
        if (!hasPermission(SecurityContext.getPrincipal()) {
            throw new AccessDeniedException();
        }
        //Security concern end here
        //Business logic start here
        Account aAct = accountRepository.findByAccountId(a);
        Account bAct = accountRepository.findByAccountId(b);
        accountRepository.transferAmount(aAct, bAct, amount);
        //
    }
}

上面的程式碼只涉及安全,再加上事務和日誌,程式碼結構就像是這個樣子的:
tangling

程式碼分散

public class TransferServiceImpl implements TransferService {
    public void transfer(Account a, Account b, Double amount) {
        //Security concern start here
        if (!hasPermission(SecurityContext.getPrincipal()) {
            throw new AccessDeniedException();
        }
        //Security concern end here
        //Business logic start here

    }
}
public class AccountServiceImpl implements AccountService {
    public void withdrawl(Account a, Double amount) {
        //Security concern start here
        if (!hasPermission(SecurityContext.getPrincipal()) {
            throw new AccessDeniedException();
        }
        //Security concern end here
        //Business logic start here

    }
}

可以看到,安全相關的程式碼分散在各個功能裡。
scattering

解決

AOP的核心術語和概念

AOP concepts

Advices實現在多點。這些點叫Joint Points,他們使用表示式定義。這些表示式叫pointcuts。

Advice

每個aspect有它的任務和目的。aspect的任務就是advice。
advice是一個任務,aspect執行該任務。這就帶來一個問題,什麼時候執行任務,任務做什麼。任務可以在業務方法呼叫前執行,也可以在業務方法執行完再執行,或者業務方法執行前後都執行,或者業務方法拋了異常才執行。業務方法有時候也叫advised method。

  • Before:在advised method執行前,呼叫advice的任務
  • After:在advised method執行完成(不能拋異常),呼叫advice的任務
  • After-returning:在advised method執行完成,返回結果後(不能拋異常),呼叫advice的任務
  • After-throwing:在advised method拋異常退出後,呼叫advice的任務
  • Around:最常用。在advised method執行前後,呼叫advice的任務

Join Point

是程式執行的一個點,比如方法呼叫或者異常丟擲。在這些點,Spring aspect插入concern功能。

Pointcut

可以定義表示式,選擇一個或者多個Join Points。這個表示式就是pointcut。

Aspect

aspect是封裝pointcuts和advice的模組。Aspects知道它要做什麼,和在哪兒、在什麼時候做。

Weaving

Weaving是aspects被組合進業務程式碼的技術。這是一個把aspects應用於目標物件的過程(通過增加新的代理物件)。
Weaving可以在編譯時、類載入時或者執行時執行。

定義pointcuts

pointcuts被用來定義advice作用的點。Spring AOP可以使用表示式(AspectJ的表示式語言的子集)定義pointcuts。

Spring支援的 描述
execution 匹配方法執行的join points
within 匹配的join points限定在一定的型別內
this 匹配的join points作用於給定型別的一個例項
target 匹配的join points作用於給定型別
args 匹配的join points作用於引數是給定型別的一個例項
@target 匹配的join points作用於有給定型別的註解的目標物件
@args 在執行時匹配join points,傳遞的實際引數有給定型別的註解
@within 匹配的join points作用於目標物件所宣告的型別有給定的註解
@annotation 匹配的join points作用於給定註解

寫pointcuts

可以這樣使用execution寫一個pointcuts:

  • execution(): 方法必須匹配pattern
  • 可以使用下列操作符鏈式組合:&& (and) , || (or) , ! (not)
  • Method pattern
    • [Modifiers] ReturnType [ClassType]
    • MethodName ([Arguments]) [throws ExceptionType]

[ ]內的引數和表示式都是可選的。

比如這樣一個介面:

package com.github.ls.test.service;

public interface TransferService {
    void transfer(String accountA, String accountB, Long amount);
}

它的實現類:

package com.github.ls.test.service.impl;

public class TransferServiceImpl {
    public void transfer(String accountA, String accountB, Long amount) {
        ///
    }
}

如果我們想在執行transfer()方法的時候,應用一個advice,可以這樣配置pointcut表示式:

  • 任何類或者包:
    • execution(void transfer*(String)):以transfer開始的任何方法,接受一個字串引數,沒有返回值
    • execution(* transfer(*)):任何叫transfer()的,接受一個引數的方法
    • execution(* transfer(int, …)):任何叫transfer的方法,其中第一個引數型別是int,後面可以跟0個或者多個引數
  • 由類限制:
    • execution(void com.github.ls.test.service.impl.TransferServiceImpl.*(…)):TransferServiceImpl類的任何void方法,包括任何子類
  • 由介面限制:
    • execution(void com.github.ls.test.service.TransferService.transfer(*)):任何帶一個引數的void transfer()方法,可以是實現TransferService的任何類
  • 使用註解:
    • execution(@javax.annotation.security.RolesAllowed void transfer*(…)):以transfer開始的任何方法,該方法還使用了@RolesAllowed註解
  • 由包限制:
    • execution(* com…test..(…)):com和test之間的一個目錄
    • execution(* com..test..*(…)):com和test之間的幾個目錄
    • execution(* …test..*(…)):任何叫test的子目錄

增加aspects

aspects是AOP最重要的一個術語。把pointcuts和advices組合在一起。

@Aspect
public class Auditing {
    //Before transfer service
    @Before("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void validate(){
        System.out.println("bank validate your credentials before amount transferring");
    }
    //Before transfer service
    @Before("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void transferInstantiate(){
        System.out.println("bank instantiate your amount transferring");
    }
    //After transfer service
    @AfterReturning("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void success(){
        System.out.println("bank successfully transferred amount");
    }
    //After failed transfer service
    @AfterThrowing("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void rollback() {
        System.out.println("bank rolled back your transferred amount");
    }
}

已經看到Auditing類如何使用@Aspect註解了。該類不只是一個Spring bean,也是一個aspect。
Auditing類的一些方法,是advices,定義了下面的邏輯:轉賬前,使用validate()做使用者認證;然後使用transferInstantiate()例項化;轉賬成功,呼叫success()方法,如果轉賬失敗,使用rollback()回滾。

Spring AOP支援五種advice註解:

Annotation Advice
@Before 使用before advice,advice的方法在advised method被呼叫前執行
@After 使用after advice,advice的方法在advised method正常執行完,或者不在乎異常後執行
@AfterReturning 使用after returning advice,advice的方法在advised method成功執行後執行
@AfterThrowing 使用after throwing advice,advice的方法在advised method拋異常退出後執行
@Around 使用around advice,advice的方法在advised method呼叫前後執行

實現Advice

Before

在這裡插入圖片描述

After Returning

在這裡插入圖片描述

After Throwing

在這裡插入圖片描述

After

在這裡插入圖片描述

Around

在這裡插入圖片描述

理解AOP代理

Spring AOP是基於代理的。Spring增加代理,在目標物件的業務邏輯之間weave aspect。
比如TransferServiceImpl類,呼叫者通過物件引用呼叫transfer()方法:
invokes

你看到了,呼叫者可以直接呼叫該service。但是,你把TransferService宣告為aspect的目標。現在,這個類被代理包裝了,呼叫者實際上不能直接呼叫該service,呼叫被路由到代理:
AOP-proxy

AOP代理是這樣作用的:

  • Spring增加代理weaving aspect和目標
  • 代理也實現了目標介面
  • 所有的transfer()呼叫都被攔截了
  • 執行匹配的advice
  • 執行目標方法