Spring 5 設計模式 - 使用代理和裝飾模式的Spring AOP
Spring 5 設計模式 - 使用代理和裝飾模式的Spring AOP
Spring中的代理模式
代理模式,就是向外界提供一個類,代表另一個類的功能。
Spring提供兩種代理
JDK proxy | CGLIB proxy |
---|---|
也叫動態代理 | 不是JDK內建的 |
API在JDK內 | 在Spring JARs內 |
要實現介面 | 沒有介面時使用 |
可代理介面 | 不能用於final類和方法(因為不能被覆蓋) |
什麼是AOP
Aspect-Oriented Programming (AOP)能夠模組化cross-cutting concerns。它是另一種程式設計正規化,補充了OOP。
OOP的關鍵要素是類和物件,AOP的關鍵要素則是aspect。Aspects允許你在程式的多個地方模組化一些功能(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);
//
}
}
上面的程式碼只涉及安全,再加上事務和日誌,程式碼結構就像是這個樣子的:
程式碼分散
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
}
}
可以看到,安全相關的程式碼分散在各個功能裡。
解決
AOP的核心術語和概念
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()方法:
你看到了,呼叫者可以直接呼叫該service。但是,你把TransferService宣告為aspect的目標。現在,這個類被代理包裝了,呼叫者實際上不能直接呼叫該service,呼叫被路由到代理:
AOP代理是這樣作用的:
- Spring增加代理weaving aspect和目標
- 代理也實現了目標介面
- 所有的transfer()呼叫都被攔截了
- 執行匹配的advice
- 執行目標方法