javascript
Spring的AOP原理
AOP是什么?
軟件工程有一個基本原則叫做“關注點分離”(Concern Separation),通俗的理解就是不同的問題交給不同的部分去解決,每部分專注于解決自己的問題。這年頭互聯網也天天強調要專注嘛!
這其實也是一種“分治”或者“分類”的思想,人解決復雜問題的能力是有限的,所以為了控制復雜性,我們解決問題時通常都要對問題進行拆解,拆解的同時建立各部分之間的關系,各個擊破之后整個問題也迎刃而解了。人類的思考,復雜系統的設計,計算機的算法,都能印證這一思想。額,扯遠了,這跟AOP有神馬關系?
面向切面編程(Aspect Oriented Programming,AOP)其實就是一種關注點分離的技術,在軟件工程領域一度是非?;鸬难芯款I域。我們軟件開發時經常提一個詞叫做“業務邏輯”或者“業務功能”,我們的代碼主要就是實現某種特定的業務邏輯。但是我們往往不能專注于業務邏輯,比如我們寫業務邏輯代碼的同時,還要寫事務管理、緩存、日志等等通用化的功能,而且每個業務功能都要和這些業務功能混在一起,痛苦!所以,為了將業務功能的關注點和通用化功能的關注點分離開來,就出現了AOP技術。這些通用化功能的代碼實現,對應的就是我們說的切面(Aspect)。
業務功能代碼和切面代碼分開之后,責任明確,開發者就能各自專注解決問題了,代碼可以優雅的組織了,設計更加高內聚低耦合了(終極目標啊!)。但是請注意,代碼分開的同時,我們如何保證功能的完整性呢? 你的業務功能依然需要有事務和日志等特性,即切面最終需要合并(專業術語叫做織入,?Weave)到業務功能中。怎么做到呢? 這里就涉及AOP的底層技術啦,有三種方式:
一個場景
接下來上例子!David對土豪老板定機票的例子比較滿意,所以決定繼續沿用這個例子。
Boss在訂機票時,我們希望能夠記錄訂機票這個操作所消耗的時間,同時記錄日志(這里我們簡單的在控制臺打印預定成功的信息)。
我們來看普通青年的做法吧:
package com.tianmaying.aopdemo;public class Boss {private BookingService bookingService;public Boss() {this.bookingService = new QunarBookingService();}//...public void goSomewhere() {long start = System.currentTimeMillis();//訂機票boolean status = bookingService.bookFlight();//查看耗時long duration = System.currentTimeMillis() - start;System.out.println(String.format("time for booking flight is %d seconds", duration));//記錄日志if (status) {System.out.println("booking flight succeeded!");} else {System.out.println("booking flight failed!");}} }我們看到,在訂機票的同時,還要處理查看耗時和記錄日志,關注的事情太多了,頭大啊。而且項目大了之后,除了訂機票之外,很多業務功能都要寫類似的代碼。讓AOP來拯救我們吧!
使用AOP的場景
相比在IoC例子中的代碼,我們讓BookingService的bookFlight()方法返回一個boolean值,表示是否預定成功。這樣我們可以演示如何獲取被切方法的返回值。
通過AOP我們怎么做呢,David今天送出獨門秘籍,告訴你通過3W方法(What-Where-When)來理解AOP。
- What:What當然指的時切面啦!首先我們將記錄消耗時間和記錄日志這兩個功能的代碼分離出來,我們可以做成兩個切面,命名為TimeRecordingAspect和LogAspect。
- Where:切面的織入發生在哪呢?切面針對的目標對象(Target)是SmartBoss(區別于Boss)!這里還有有一個很關鍵的概念叫做切入點(Pointcut),在這個場景中就是指在SmartBoss調用什么方法的時候的時候應用切面。顯然,我們希望增強的是bookFlight()方法,即在bookFlight方法調用的地方,我們加入時間記錄和日志。
- When: 什么時候織入呢?這涉及到織入的時機問題,我們可以在bookFlight()執行前織入,執行后織入,或者執行前后同時切入。When的概念用專業術語來說叫做通知(Advice)。
了解了3W之后,來看看代碼吧,先上LogAspect:
插入一段,POM文件不要忘記了引入Spring AOP相關的依賴:
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.2.0.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>4.2.0.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.5</version></dependency> <dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.5</version></dependency>LogAspect
package com.tianmaying.aopdemo.aspect;import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect //1 @Component public class LogAspect {@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))") //2private void logPointCut() {}@AfterReturning(pointcut = "logPointCut()", returning = "retVal") //3public void logBookingStatus(boolean retVal) { //4if (retVal) {System.out.println("booking flight succeeded!");} else {System.out.println("booking flight failed!");}} }我們看這段代碼:
1通過一個@Apsect標注,表示LogAspect是一個切面,解決了What問題。2通過定義一個標注了@Pointcut的方法,定義了Where的問題,"execution(* com.tianmaying.aopdemo..*.bookFlight(..))"表示在com.tianmaying.aopdemo包或者子包種調用名稱為bookFlight的地方就是切入點!定義Pioncut的語法這里不詳解了,David這里要告訴你的時它的作用:解決Where的問題!3通過一個@AfterReturning標注表示在bookFlight()調用之后將切面織入,這是一個AfterReturning類型的Advice,注意這里可以通過returning屬性獲取bookFlight()的返回值。4這里定義了實現切面功能的代碼,經過這么一番閃轉騰挪,最后寫日志的代碼跑到這里來了!
再來看TimeRecordingAspect:
TimeRecordingAspect:
package com.tianmaying.aopdemo.aspect;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;@Aspect @Component public class TimeRecordingAspect {@Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))")private void timeRecordingPointCut() {}@Around("timeRecordingPointCut()") //1public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { //2long start = System.currentTimeMillis();Object retVal = pjp.proceed(); // 3long duration = System.currentTimeMillis() - start;System.out.println(String.format("time for booking flight is %d seconds", duration));return retVal;} }- 與LogAspect不同,因為要計算bookFlight()的耗時,我們必須在調用前后到切入代碼,才能算出來這之間的時間差。因此,在1處,我們定義的是一個Around類型的Advice。
- 2處是實現AroundAdvice的方法,其方法的參數和返回值是固定寫法。
- 3處也是固定寫法,表示對目標方法(即bookFlight())的調用,注意不要漏了,漏掉的話原方法就不會被調用了,通常情況下肯定不是你想要的結果!
回頭再看SmartBoss的代碼,比Boss簡單多了,goSomewhere()方法中只剩下一條語句,酷的掉渣啊,只關注訂機票,其他的事情都F**k Off吧!
package com.tianmaying.aopdemo;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class SmartBoss {private BookingService bookingService;//...public void goSomewhere() {bookingService.bookFlight();} }當然,要讓代碼Run起來,還需要在App類中加上@EnableAspectJAutoProxy標注,這樣Spring啟動時就去去掃描AOP相關的標注,在創建對象時幫我們去執行織入過程!
回到定義
例子講完!現在我們再來逐一看看AOP中的那些名詞定義,這個時候理解這些概念,你應該不會覺得冷冰冰了,應該要有一種"Ya!"的感覺啦!
-
切面(Aspect):指的就是通用功能的代碼實現,比如我們上面演示的時間記錄切面,日志切面,它們都是普通的Java類:TimeRecordingAspect和LogAspect。
-
目標對象(Target):要被織入切面的對象,例子中的CtripBookingService,有了AOP,它們可以專注于核心業務邏輯代碼了!
-
切入點(Pointcut):定義通知應該切入到什么地方,Spring支持的切入點就是方法調用,切入點的定義可以使用正則表達式,用以描述什么類型的方法調用。@Pointcut就是用來定義切入點的。
-
通知(Advice):切面是一個類,而通知就是類里的方法以及這個方法如何織入到目標方法的方式(用@AfterReturning和@Around標注的方法)。我們的例子中只展示了兩類通知,根據織入到目標方法方式的不同,一共可以分為5種:
- 前置通知(Before)
- 后置通知(AfterReturning)
- 異常通知(AfterThrowing)
- 最終通知(After)
- 環繞通知(Around)
- 織入(Weaving):AOP實現的過程,即將切面應用到目標對象,從而創建一個新的代理對象的過程,對于Spring來說,就是初始化Context中的對象時,完成織入操作。
現在你應該理解Spring中AOP的關鍵知識和核心原理了,剩下的就是在David給你的3W框架下,去學習每一部分的知識了,比如不同類型通知的寫法,PointCut的各種類型的定義方法,切面和目標對象之間參數傳遞,等等。當然,最關鍵的還是趕緊實踐中用起來!
版權聲明本文由 David創作,轉載需署名作者且注明文章出處參考代碼要獲取本文的參考代碼,請訪問:?https://www.tianmaying.com/tutorial/spring-aop/repo
from:?https://www.tianmaying.com/tutorial/spring-aop
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Spring的AOP原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java字符串格式化:String.fo
- 下一篇: ECharts的简单使用过程