Spring AOP 程式設計
Spring AOP 程式設計
什麼是 AOP
AOP(Aspect Oriented Programming 的縮寫,翻譯為面向方面或面向切面程式設計),通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術
- AOP 是 OOP 的延續和有益補充,也是 Spring 框架中的一個重要內容,是函數語言程式設計的一種衍生範型
- 在 Spring 中提供了 AOP 的豐富支援,允許通過分離應用的業務邏輯與系統級服務和事務管理進行內聚性的開發
利用 AOP 程式設計可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低(低耦合),提高程式的可重用性,同時提高開發的效率
AOP 與 OOP 的區別
AOP(面向切面程式設計) 和 OOP(面向物件程式設計) 在字面上雖然非常類似,但卻是面向不同領域的兩種程式設計思想,這兩種程式設計思想在目標上有著本質的差異
- OOP(面向物件程式設計):針對業務處理過程的實體及其屬性和行為進行抽象封裝為物件,以物件作為最基本的邏輯處理單元,並關注物件與物件之間的關係
- AOP(面向切面程式設計):針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以切面作為最基本的邏輯處理單元,以獲得邏輯過程中各部分之間低耦合性的隔離效果
AOP 絕對不會代替 OOP,而是與 OOP 整合起來,以此之長,補彼之短
橫切關注點
在軟體開發中,分佈於應用中多處的功能被稱為橫切關注點
- 通常,這些橫切關注點從概念上是與應用的業務邏輯分離的(但往往直接嵌入到應用的業務邏輯之中),AOP 的目標正是將這些橫切關注點與業務邏輯隔離開來
- DI(依賴注入)有助於應用物件之間的解耦,而 AOP 可以實現橫切關注點與他們所影響的物件之間的解耦
AOP 主要功能
- 日誌記錄
- 效能統計
- 安全控制
- 事務處理
- 異常處理
- 其他功能
AOP 橫向抽取機制
AOP 通過橫向抽取機制為這類無法通過縱向繼承體系(OOP 的方式)進行抽象的重複性程式碼提供瞭解決方案
AOP 就是將各個業務邏輯程式碼中的相同程式碼,通過橫向切割的方式抽取到一個獨立的模組中,讓業務邏輯程式碼更加清晰
AOP 術語
連線點(Joinpoint)
程式執行的某個特定位置(如類開始初始化前、類初始化後。類某個方法呼叫前、呼叫後、方法跑吹異常後)。一個類或一段程式程式碼擁有一些具有邊界性質的特定點,這些程式碼中的特定點就稱為連線點(插入程式碼的位置)
注意:Spring AoP 僅支援方法的連線點,即僅能在方法呼叫前、方法呼叫後、方法丟擲異常時以及方法呼叫前後這些程式執行點織入增強
切點(Pointcut)
每個類一般都擁有多個連線點(一般一個方法就是一個連線點)。AOP需要定位到特定的連線點,而定位連線點的方式稱為切點。連線點相當於資料庫中的記錄,而切點相當於查詢條件,一個切點可以匹配多個連線點。(插入程式碼位置的查詢條件)
注意:Spring AOP 中切點通過 Pointcut 介面定義,它使用類和方法作為連線點的查詢條件。Spring AOP 的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連線點
增強(Advice)
增強是織入到目標類連線點上的一段程式程式碼。在 Spring AOP 中,增強除用於描述一段程式程式碼外,還擁有另一個和連線點相關的資訊-執行點的方位。結合執行點的方位資訊和切點資訊,AOP就可以找到特定的連線點。
因為增強既包括了用於新增到目標連線點上的一段執行邏輯,由包含用於定位連線點的方位資訊,所以Spring所提供的增強介面都帶有方位名
注意:增強有的教材也叫通知,是對 Advice 翻譯不同而已
引介(Introduction)
引介是一種特殊的增強,它為類新增一些屬性和方法。即使一個業務類原本沒有實現某個介面,也可以通過AOP引介功能,動態地位該業務類新增介面的實現邏輯,讓業務類成為這個介面的實現類。
目標物件(Target)
它是增強邏輯的織入目標類。通過 AOP,業務邏輯類只需要實現非橫切邏輯的程式碼,而效能監視、事務管理等橫切邏輯則可以使用 AOP 動態織入到特定的連線點上。
織入(Weaving)
織入是將增強新增對目標類具體連線點上的過程
AOP 有三種織入方式:
- 編譯器織入:要求使用特殊的編譯器
- 類裝載期織入:要求使用特殊的類裝載器
- 動態代理織入:在執行期為目標類新增增強生成子類的方式
注意:Spring AOP 採用動態代理織入方式
代理(Proxy)
一個類被 AOP 織入增強後,就產出了一個結果類,它是融合了原類和增強邏輯的代理類。根據不同的代理方式,代理類既可能是和原類具有相同介面的類,也可能就是原類的子類。所以,可以採用呼叫原類相同的方式呼叫代理類。
代理設計模式:目標類的許可權控制和增加目標類的功能
切面(Aspect)
切面由切點和增強或引介組成。它既包括了橫切邏輯的定義,也包括了連線點的定義。
切面 = 切點 + 增強(或引介)
注意:Spring AOP 負責實施切面的框架,它將切面所定義的橫切邏輯織入到切面所指定的連線點中
AOP 框架
JDK 動態代理與 CGLib 動態代理區別
JDK 的動態代理機制只能代理實現了介面的類,而不能實現介面的類就不能實現 JDK 的動態代理;CGLib 是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對 final 修飾的類進行代理
增強
Spring AOP 框架使用增強介面定義橫切邏輯,同時由於 Spring AOP 只支援方法連線點,所以增強既包含橫切邏輯,還包含部分連線點資訊
切面型別
StaticMethodMatcherPointcutAdvisor(靜態切面)
StaticMethodMatcherPointcutAdvisor 代表一個靜態方法匹配切面,它通過StaticMethodMatcherPointcut 定義切點,通過類過濾和方法名匹配定義切點(開發者需要繼承該類切面,並提供匹配類過濾和方法匹配的規則)
DynamicMethodMatcherPointcut(動態切面)
DynamicMethodMatcherPointcut抽象類,它將isRuntime()方法設定為final 且返回 true,這樣其繼承子類就一定是一個動態的切點。該抽象類預設匹配所有的類和方法,因此實現動態切面需要繼承該類。
切面機制
Spring 採用的機制是在建立代理時對目標類的每個連線點使用靜態切點檢查,如果僅通過靜態切點檢查就可以知道連線點是不匹配的,則在執行時就不再進行動態檢查;如果靜態切點檢查是匹配的,在執行時才進行動態切點檢查
- 由於動態切點檢查會對效能造成很大的影響,應當儘量避免在執行時每次都對目標類的各個方法進行動態檢查
- 在動態切點類定義靜態切點檢查的方法可以防止不必要的動態檢查操作,可以極大地提高執行效率
- 動態切面的配置和靜態切面的配置沒有太大區別,使用DefaultPointcutAdvisor 定義切面
切面註解
開啟 AOP 註解
<aop:aspectj-autoproxy />
@Aspect 註解(宣告切面類)
該註解作用於切面類上,一般採用 Spring 提供的自動掃描元件的方式註冊 Bean,所以註解切面類上還要配置 @Component;否則就要把該切面類註冊到 Spring 的 XML 配置檔案中
@Before 註解(前置增強)
用於註解實現前置增強,作用於在切面類方法上
@Before("execution(返回型別 類全名.方法名(形參列表))")
public void 方法名( ) {
//方法實現
…
}
@After 註解(後置增強)
用於註解實現後置增強,作用於在切面類方法上
@After("execution(返回型別 類全名.方法名(形參列表))")
public void 方法名( ) {
//方法實現
…
}
@Around 註解(環繞增強)
用於註解實現環繞增強,作用於在切面類方法上
@Around("execution(返回型別 類全名.方法名(形參列表))")
public void 方法名(ProceedingJoinPoint jp) {
//方法前執行增強程式碼
Object obj = jp.processed();
//方法後執行增強程式碼
return obj;
}
@AfterThrowing 註解(丟擲異常增強)
用於註解實現增強,作用於在切面類方法上
@AfterThrowing (“execution(返回型別 類全名.方法名(形參列表))”)
public void 方法名( ) {
//方法實現
…
}
切點表示式
execution( 返回型別 類全名.方法名(形參列表))
表示式萬用字元如下:
切點表示式舉例:
execution(* com.lovo.bean.Dog.b*())
execution(* com.lovo.*.Dog.b*(..))
execution(* com.lovo..Dog.b*(..))
execution(* com lovo..Dog.b*(*))//(*)代表一個形參
execution(* com.lovo.bean.Dog+.b*(..))
Spring 宣告式事務管理
@Transactional 註解
作為使用基於 XML 配置宣告式事務配置方法的補充, 你可以使用一種基於註解的方法. 直接在Java 程式碼中宣告事務 語義宣告使得宣告更加靠近生效的程式碼. 這不存在過度危險的耦合, 因為不管怎麼說開發程式碼的就意味著這樣被事務化地使用
舉例:
// 我們想要支援事務的服務類
@Transactional
Public class Service implements IService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
Spring 整合 JUnit 單元測試
@RunWith(SpringJUnit4ClassRunner.class) //使用 Junit 4 單元測試框架
@ContextConfiguration("classpath:applicationContext.xml") //載入 ioc 容器
@Transactional(transactionManager = "transactionManger") //宣告式事務管理
@Rollback(value = false) //不對事務進行回滾(預設true)
public class UserDaoTest extends AbstractTransactionalJUnit4SpringContextTests {
@Test
public void test() {
//測試程式碼
}
}