附上示例程序的github地址:https://github.com/bjtudujunlin/SpringDataExample
1、 AOP定義
AOP作爲Spring的核心功能之一,用來解決服務之間依賴的耦合問題,通過定義切點,實現服務分離,將普遍依賴的非業務服務從業務服務之中分離開來。AOP的理論知識見上一章節Spring框架系列(一)-整體架構。
Spring中AOP借鑑了AspectJ的實現,以提供註解驅動,編程模型幾乎與編寫成熟的AspectJ註解切面完全一致。Spring AOP和AspectJ的區別在於兩者作用範圍不同,Spring AOP的切點只能定義在對象方法上,AspectJ不僅能提供方法的攔截,還能提供構造器或屬性攔截。實際運用中根據自己需求進行選擇,一般基於方法的攔截已經能夠滿足日常應用。
Spring AOP支持的通知類型如下:
前置通知(Before) |
在目標方法被調用之前調用通知功能 |
後置通知(After) |
在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼 |
返回通知(After-returning) |
在目標方法成功執行之後調用通知; |
異常通知(After-throwing) |
在目標方法拋出異常後調用通知; |
環繞通知(Around) |
通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行爲 |
2、 應用場景
AOP適用於哪些場景呢,下面將通過舉例進行說明。
A、日誌記錄,跟蹤,優化和監控
在軟件測試中,如果想在一個耗時嚴重的操作中找出其耗時的瓶頸時,一般採用的方法是在每個被調用的函數中寫進測試代碼,在運行時打出日誌。如果該操作涉及到的業務邏輯特別複雜時,插入這些測試代碼不僅工作量十分巨大,而且難以維護。如果後期剔除不乾淨,不僅增加了無關的代碼量,還會在執行時造成不必要的資源浪費。
這種情況下使用AOP攔截業務方法的調用,在通知函數裏面打印調用時間,監控各個方法耗時,實現非侵入式攔截,避免使用大量測試代碼。
B、 事務處理
事務是單個邏輯工作單元執行的一系列操作,要麼完整地執行,要麼完全地不執行。事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。通過將一組相關操作組合爲一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢復並使應用程序更加可靠。一個邏輯工作單元要成爲事務,必須滿足所謂的ACID(原子性、一致性、隔離性和持久性)屬性。
利用AOP捕獲某方法,在方法執行前開始事務,在異常通知函數中統一執行事務回滾操作,方法執行結束後關閉事務。
C、 持久化
狹義的理解: “持久化”僅僅指把域對象永久保存到數據庫中;廣義的理解,“持久化”包括和數據庫相關的各種操作(持久化就是將有用的數據以某種技術保存起來,將來可以再次取出來應用,數據庫技術,將內存數據一文件的形式保存在永久介質中(磁盤等)都是持久化的例子.)。
持久化實際上是數據庫的事務處理,Mybatis、Hibernate等都有自己的一套事務管理機制,但需要在每個添加事務處理的方法中加入開始事務、結束事務的代碼,維護不方便,使用AOP可以解決這個問題,Spring提供了一些內置的事務管理器,如DataSourceTransactionManager、HibernateTransactionManager、JtaTransactionManager等
D、資源池
在開發過程中,我們常會用到一些資源池,比如線程池、數據庫連接池。在操作這些資源池之後,往往要將一開始得到的資源池對象釋放回資源池。代碼的一般實現如下:
try { //從資源池獲取一個資源,進行業務處理 } catch (Exception e) { //異常處理 }finally { //釋放資源到資源池 } |
在所有調用使用資源池的地方都加入這樣的代碼會顯得特別繁瑣,管理困難。可以使用AOP進行解決。利用AOP添加業務方法的環切操作@Around,將所有獲取資源、異常處理和釋放資源的操作統一在通知方法中實現,業務方法中只需處理獲取資源後的業務。
E、 系統統一的認證、權限管理等
這個也比較好理解,攔截對方法的訪問,在通知方法裏面加入認證和權限管理代碼,實現統一的認證管理。
F、 應用系統的異常捕捉及處理
將系統異常統一進行處理,避免了大量try catch代碼。
3、 Spring AOP實現
這裏提供基於java註解的aop實現。本篇代碼在Spring框架系列(一)-整體架構例子中進行追加。
A、 添加maven依賴
<properties> <spring.version>4.3.3.RELEASE</spring.version> <aspectj.version>1.6.12</aspectj.version> </properties> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${aspectj.version}</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> |
B、 添加切點
定義切點通過切點指示器來實現,指示器的表達式定義如下,在實例中,execution(public *iscas.springstudy.Person.Introduce(..))就表示在iscas.springstudy.Person.Introduce這個方法調用時創建切點。
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.context.annotation.Configuration;
@Configuration @Aspect public class VisitLog {
//定義切點, @Pointcut("execution(public * iscas.springstudy.Person.Introduce(..))") public void myMethod(){};
@Before("myMethod()") public void logPeronIntroduce(JoinPoint point){ //通過JoinPoint參數可以獲取到攔截的方法、參數等 System.out.println("person introduce "+point.getSignature().getDeclaringTypeName()); }
@After("myMethod()") public void logPeronIntroduceEnd(){ System.out.println("end visit person introduce"); }
@Around("myMethod()") public void logPeronIntroduceAround(ProceedingJoinPoint point){ try { System.out.println("before around visit person introduce"); point.proceed();//執行被攔截的方法 System.out.println("after around visit person introduce"); } catch (Throwable e) { System.out.println("exception around visit person introduce"); }
} } |
C、 Spring Xml配置
添加aspect支持,添加自動掃描並把configuration類所在的路徑
<!-- 配置aop --> <context:component-scan base-package="iscas.springstudy.aop" /> <!-- 自動掃描 --> <aop:aspectj-autoproxy /> |