AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和運作期動态代理實作程式功能的統一維護的一種技術。AOP是Spring架構中的一個重要内容,它通過對既有程式定義一個切入點,然後在其前後切入不同的執行内容,比如常見的有:打開資料庫連接配接/關閉資料庫連接配接、打開事務/關閉事務、記錄日志等。基于AOP不會破壞原來程式邏輯,是以它可以很好的對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
下面主要講兩個内容,一個是如何在Spring Boot中引入Aop功能,二是如何使用Aop做切面去統一處理Web請求的日志。
以下所有操作基于chapter4-2-2工程進行。
準備工作
因為需要對web請求做切面來記錄日志,是以先引入web子產品,并建立一個簡單的hello請求的處理。
- pom.xml中引入web子產品

- 實作一個簡單請求處理:通過傳入name參數,傳回“hello xxx”的功能。
下面,我們可以對上面的/hello請求,進行切面日志記錄。
引入AOP依賴
在Spring Boot中引入AOP就跟引入其他子產品一樣,非常簡單,隻需要在pom.xml中加入如下依賴:
在完成了引入AOP依賴包後,一般來說并不需要去做其他配置。也許在Spring中使用過注解配置方式的人會問是否需要在程式主類中增加@EnableAspectJAutoProxy來啟用,實際并不需要。
可以看下面關于AOP的預設配置屬性,其中spring.aop.auto屬性預設是開啟的,也就是說隻要引入了AOP依賴後,預設已經增加了@EnableAspectJAutoProxy。
而當我們需要使用CGLIB來實作AOP的時候,需要配置spring.aop.proxy-target-class=true,不然預設使用的是标準Java的實作。
實作Web層的日志切面
實作AOP的切面主要有以下幾個要素:
- 使用@Aspect注解将一個java類定義為切面類
- 使用@Pointcut定義一個切入點,可以是一個規則表達式,比如下例中某個package下的所有函數,也可以是一個注解等。
- 根據需要在切入點不同位置的切入内容
- 使用@Before在切入點開始處切入内容
- 使用@After在切入點結尾處切入内容
- 使用@AfterReturning在切入點return内容之後切入内容(可以用來對處理傳回值做一些加工處理)
- 使用@Around在切入點前後切入内容,并自己控制何時執行切入點自身的内容
- 使用@AfterThrowing用來處理當切入内容部分抛出異常之後的處理邏輯
可以看上面的例子,通過@Pointcut定義的切入點為com.didispace.web包下的所有函數(對web層所有請求處理做切入點),然後通過@Before實作,對請求内容的日志記錄(本文隻是說明過程,可以根據需要調整内容),最後通過@AfterReturning記錄請求傳回的對象。
通過運作程式并通路:http://localhost:8080/hello?name=didi,可以獲得下面的日志輸出
優化:AOP切面中的同步問題
在WebLogAspect切面中,分别通過doBefore和doAfterReturning兩個獨立函數實作了切點頭部和切點傳回後執行的内容,若我們想統計請求的處理時間,就需要在doBefore處記錄時間,并在doAfterReturning處通過目前時間與開始處記錄的時間計算得到請求處理的消耗時間。
那麼我們是否可以在WebLogAspect切面中定義一個成員變量來給doBefore和doAfterReturning一起通路呢?是否會有同步問題呢?
的确,直接在這裡定義基本類型會有同步問題,是以我們可以引入ThreadLocal對象,像下面這樣進行記錄:
優化:AOP切面的優先級
由于通過AOP實作,程式得到了很好的解耦,但是也會帶來一些問題,比如:我們可能會對Web層做多個切面,校驗使用者,校驗頭資訊等等,這個時候經常會碰到切面的處理順序問題。
是以,我們需要定義每個切面的優先級,我們需要@Order(i)注解來辨別切面的優先級。i的值越小,優先級越高。假設我們還有一個切面是CheckNameAspect用來校驗name必須為didi,我們為其設定@Order(10),而上文中WebLogAspect設定為@Order(5),是以WebLogAspect有更高的優先級,這個時候執行順序是這樣的:
- 在@Before中優先執行@Order(5)的内容,再執行@Order(10)的内容
- 在@After和@AfterReturning中優先執行@Order(10)的内容,再執行@Order(5)的内容
是以我們可以這樣子總結:
- 在切入點前的操作,按order的值由小到大執行
- 在切入點後的操作,按order的值由大到小執行