AOP(Aspect Oriented Programming):面向切面編程
- 可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。
- 利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性。
- 在Spring中提供了面向切面編程的豐富支持,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發。應用對象只實現它們應該做的——完成業務邏輯——僅此而已。它們並不負責(甚至是意識)其它的系統級關注點,例如日誌或事務支持。
- 將公共功能提取出來可以極大的提升開發效率 同時提高了開發的效率。
- 以往是通過繼承體系來達到代碼重用,如果將繼承體系看做一種自上而下的樹狀結構,那麼繼承是一種縱向的代碼重用,隨着時間推移軟件行業又提出了aop的概念及面向切片編程,aop可以看做是橫向的代碼重用,
主要功能:
日誌記錄,性能統計,安全控制,事務處理,異常處理,數據驗證,數據加解密,爲了保證對象在併發下只有一個對象訪問的加鎖和解鎖等等。
首先日誌、數據驗證、加解密這些代碼不應該寫在需求中,這樣代碼重複混亂臃腫不靈活,也會帶來很大的工作量。比如日誌需求發生變化,就要修改所有的模塊。
主要意圖:
將共用性系統功能(日誌、性能、安全、異常等等)和其他共用功能(數據驗證,加解密)和業務邏輯分離;
AOP和OOP(Object Oriented Programming)的區別:
OOP針對業務處理過程的實體及其屬性和行爲進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
AOP則是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合的隔離效果。
如果說面向對象編程是關注將需求功能劃分爲不同的並且相對獨立,封裝良好的類,並讓它們有着屬於自己的行爲,依靠繼承和多態等來定義彼此的關係的話;那麼面向切面編程則是希望能夠將通用需求功能從不相關的類當中分離出來,能夠使得很多類共享一個行爲,一旦發生變化,不必修改很多類,而只需要修改這個行爲即可。
面向對象編程主要用於爲同一對象層次的公用行爲建模。它的弱點是將公共行爲應用於多個無關對象模型之間。而這恰恰是面向切面編程適合的地方。有了 AOP,我們可以定義交叉的關係,並將這些關係應用於跨模塊的、彼此不同的對象模型。AOP 同時還可以讓我們層次化功能性而不是嵌入功能性,從而使得代碼有更好的可讀性和易於維護。它會和麪向對象編程合作得很好。 oop對象層面建模,AOP業務層面建模
AOP示意圖:
原理
AOP是通過動態代理實現的,動態代理又分爲兩個部分:JDK動態代理和CGLIB動態代理,AOP功能的使用還是比較簡單的,把相關bean注入到Spring容器中,編寫好相應的Aspect類即可,以下兩點需要記住:
1、AOP基於
動態代理模式
2、AOP是方法級別
的
概念:
切面(Aspect):
橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象,Aspect 聲明類似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的 Advice。一個關注點的模塊化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關於橫切關注點的很好的例子。在Spring AOP中,切面可以使用基於模式或者基於
@Aspect
註解的方式來實現
切點(pointcut):
匹配連接點的斷言。通知和一個切入點表達式關聯,並在滿足這個切入點的連接點上運行(例如,當執行某個特定名稱的方法時),切入點表達式如何和連接點匹配是AOP的核心:Spring
缺省使用AspectJ切入點語法
連接點(Joinpoint):
在程序執行過程中某個特定的點,比如某方法調用的時候或者處理異常的時候。在SpringAOP中,一個連接點總是表示一個方法的執行
通知(Advice):
在切面的某個特定的連接點上執行的動作。其中包括了
Around
、Before
和After
等不同類型的通知。許多AOP框架(包括Spring)都是以攔截器做通知模型,並維護一個以連接點爲中心的攔截器鏈
引入(Intorduction)
用來給一個類型聲明額外的方法或屬性(也被稱爲連接類型聲明)。Spring允許引入新的接口(以及一個對應的實現)到任何被代理的對象。例如,你可以使用引入來使一個bean實現接口,以便簡化緩存機制
目標對象(Target Object)
被一個或者多個切面所通知的對象。也被稱做被通知對象。既然Spring AOP是通過運行時代理實現的,這個對象永遠是一個被代理對象
AOP代理(Aop proxy)
AOP框架創建的對象,用來實現切面契約(例如通知方法執行等等)。在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理
植入(weaving)
把切面連接到其它的應用程序類型或者對象上,並創建一個被通知的對象。這些可以在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其他純Java AOP框架一樣,在運行時完成織入
每個出口都是連接點,但是我們使用的那個出口纔是切點。每個應用有多個位置適合織入通知,這些位置都是連接點。但是隻有我們選擇的那個具體的位置纔是切點。
二、常用切入點指示符
1、execution表達式
用於匹配方法執行的連接點,屬於方法級別
語法:execution(修飾符 返回值類型 方法名(參數)異常)
語法參數 | 描述 |
---|---|
修飾符 | 可選,如public,protected,寫在返回值前,任意修飾符填* 號就可以 |
返回值類型 | 必選 ,可以使用* 來代表任意返回值 |
方法名 | 必選 ,可以用* 來代表任意方法 |
參數 | () 代表是沒有參數,(..) 代表是匹配任意數量,任意類型的參數,當然也可以指定類型的參數進行匹配,如要接受一個String類型的參數,則(java.lang.String) , 任意數量的String類型參數:(java.lang.String..) 等等。。。 |
異常 | 可選,語法:throws 異常 ,異常是完整帶包名,可以是多個,用逗號分隔 |
符號:
符號 | 描述 |
---|---|
* | 匹配任意字符 |
.. | 匹配多個包或者多個參數 |
+ | 表示類及其子類 |
條件符:
符號 | 描述 |
---|---|
&&、and | 與 |
|| | 或 |
! | 非 |
案例:
- 攔截com.gj.web包下的所有子包裏的任意類的任意方法
execution(* com.gj.web..*.*(..))
- 攔截com.gj.web.api.Test2Controller下的任意方法
execution(* com.gj.web.api.Test2Controller.*(..))
- 攔截任何修飾符爲public的方法
execution(public * * (..))
- 攔截com.gj.web下的所有子包裏的以ok開頭的方法
execution(* com.gj.web..*.ok*(..))
更多用法大家可以根據語法自行設計,本文不在進行舉例
2、@annotation
根據所應用的註解對方法進行過濾
語法:@annotation(註解全路徑)
實例:
對用了com.gj.annotations.Test註解的所有方法進行攔截@annotation(com.gj.annotations.Test)
3、Within
根據類型(比如接口、類名或者包名過濾方法)進行攔截
語法:within(typeName)
示例:
- 對com.gj.web下的所有子包的所有方法進行攔截
within(com.gj.web..*)
更多用法可以根據語法自行設計
4、@Within
用於匹配所有持有指定註解類型內的方法,與within是有區別的,within是用於匹配指定類型內的方法執行,而@within是指定註解類型內的方法
5、bean
Spring AOP擴展的,AspectJ沒有對於它的指示符,用於匹配特定名稱的bean對象的執行方法
語法:bean(beanName)
示例:bean(testController)
三、AOP通知
在切面類中需要定義切面方法用於響應響應的目標方法,切面方法即爲通知方法,通知方法需要用註解標識,AspectJ支持5種類型的通知註解
註解 | 描述 |
---|---|
@Before | 前置通知, 在方法執行之前執行 |
@After | 後置通知, 在方法執行之後執行 |
@AfterReturn | 返回通知, 在方法返回結果之後執行 |
@AfterThrowing | 異常通知, 在方法拋出異常之後 |
@Around | 環繞通知,圍繞方法的執行 |
1、@Before
1、@Before
示例:
@Before("testCut()")
public void cutProcess(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP開始攔截, 當前攔截的方法名: " + method.getName());
}
2、@After
示例:
@After("testCut()")
public void after(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP執行的方法 :"+method.getName()+" 執行完了");
}
3、@AfterReturn
其中value
表示切點方法,returning
表示返回的結果放到result這個變量中
示例:
/**
* returning屬性指定連接點方法返回的結果放置在result變量中
* @param joinPoint 連接點
* @param result 返回結果
*/
@AfterReturning(value = "testCut()",returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP攔截的方法執行成功, 進入返回通知攔截, 方法名爲: "+method.getName()+", 返回結果爲: "+result.toString());
}
4、@AfterThrowing
其中value
表示切點方法,throwing
表示異常放到e這個變量
示例:
@AfterThrowing(value = "testCut()", throwing = "e")
public void afterThrow(JoinPoint joinPoint, Exception e) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP進入方法異常攔截, 方法名爲: " + method.getName() + ", 異常信息爲: " + e.getMessage());
}
5、Around
示例:
@Around("testCut()")
public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("註解方式AOP攔截開始進入環繞通知.......");
Object proceed = joinPoint.proceed();
System.out.println("準備退出環繞......");
return proceed;
}
四、SpringBoot中使用
1、創建註解
註解用於進行AOP攔截
/**
* @author Gjing
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
2、創建一個接口
/**
* @author Gjing
**/
@RestController
@RequestMapping("test1")
public class TestController {
@GetMapping("/ok")
@ApiOperation(value = "測試1", httpMethod = "GET")
@ApiImplicitParam(name = "id", value = "id值", dataType = "int", paramType = "query")
@Test
@NotNull
public String test2(Integer id) {
return "ok";
}
}
3、創建切面類
/**
* @author Gjing
**/
@Aspect
@Component
public class TestAspect {
@Pointcut("@annotation(com.gj.annotations.Test)")
public void testCut() {
}
@Before("testCut()")
public void cutProcess(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP開始攔截, 當前攔截的方法名: " + method.getName());
}
@After("testCut()")
public void after(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP執行的方法 :"+method.getName()+" 執行完了");
}
@Around("testCut()")
public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("註解方式AOP攔截開始進入環繞通知.......");
Object proceed = joinPoint.proceed();
System.out.println("準備退出環繞......");
return proceed;
}
/**
* returning屬性指定連接點方法返回的結果放置在result變量中
* @param joinPoint 連接點
* @param result 返回結果
*/
@AfterReturning(value = "testCut()",returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP攔截的方法執行成功, 進入返回通知攔截, 方法名爲: "+method.getName()+", 返回結果爲: "+result.toString());
}
@AfterThrowing(value = "testCut()", throwing = "e")
public void afterThrow(JoinPoint joinPoint, Exception e) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("註解方式AOP進入方法異常攔截, 方法名爲: " + method.getName() + ", 異常信息爲: " + e.getMessage());
}
}
AOP實現併發對共享數據訪問
1.樂觀鎖,如果是數據庫中的數據添加版本號比較;
2.提供一些必備的功能,對被訪問對象實現加鎖或解鎖功能。以保證所有在修改數據對象的操作之前能夠調用lock()加鎖,在它使用完成後,調用unlock()解鎖。