一:AOP的背景
面試的時候面試官讓我解釋一下什么是AOP,當時不懂,在路上就查了,AOP:面向切面的編程技術,困惑了,JAVA是OOP:面向對象的編程技術。那么自己就立刻查了幾個為題:1、什么是面向切面的編程技術;2、為什么要面向切面的編程技術;3、與OOP是什么關系?
首先解釋第二個問題:在我們平時的開發過程中,你肯定會遇到下面幾個面:1)權限校驗;2)業務的核心代碼;3)記錄日志。那么在@Service層采用代碼累加的方法,那么結構就會如下。
@Service public class myService{ @Resource private CoreService coreService; @Resource private LogService logService; @Resource private PropertyService propertyService; // 權限校驗代碼 //核心業務層代碼 //記錄日志的代碼
// 異常的處理
}
從上面的代碼結構中我們可以看出以下幾個問題:
1.1、代碼混亂:核心業務模塊與其他非核心的代碼交織在一起,大大影響了代碼的模塊獨立性能,不利於代碼的維護,而且分工不明確造成代碼混亂。
1.2、冗余代碼:其實權限的校驗,異常的處理,日志的記錄可以獨立在一個模塊給所有的服務公用,寫在一起導致代碼的分散和冗余。
因此面向切面的編程技術應運而生。
解釋第一個問題:什么是面向切面的編程技術。切面與切點是幾何上面的術語,用在這里可以這樣理解:將核心業務代碼過程比作一個柱體,其他的日志記錄,權限校驗等就像是橫切核心業務的面,這些面需要完成一些非核心的業務。如下圖:
從圖中可以看出我們定義了多個切面,每個切面都完成各自的非核心的業務,一個切面上還可以完成多個非核心的業務。
1.3、第三個問題:與OOP是什么關系?
AOP的實現技術有多種,其中與Java無縫對接的是一種稱為AspectJ的技術,Spring AOP 與AspectJ 實現原理上並不完全一致,但功能上是相似的。AOP的出現確實解決外圍業務代碼與核心業務代碼分離的問題,但它並不會替代OOP,如果說OOP的出現是把編碼問題進行模塊化,那么AOP就是把涉及到眾多模塊的某一類問題進行統一管理(參考:關於 Spring AOP (AspectJ) 你該知曉的一切;其實我也想到了他們的關系,但是感覺沒有這篇博客總結的很好)
二:AOP的核心概念
從上右圖可以可以很好看到切面(Aspect)包含了切點(PointCut)、連接點(JoinPoint);額外還有通知(Advice),織入(Weaving),引入(Introduce)。
package springMVCmybatis.com.my.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.springframework.core.annotation.Order; @Aspect // 切面執行順序 @Order(3) public class MyAopTest { @Pointcut("execution(* springMVCmybatis..addController.addEmp(..))") private void pointCutMethod() { } @Pointcut("execution(* springMVCmybatis.com.my.aop.UserServiceImp.*(..))") private void testAOP() { } /* * 聲明前置通知 ,JoinPont是srpring提供的靜態變量, * 通過joinPoint參數可以獲得目標方法的類名,方法參數,方法名等信息,這個參數可有可無。 */ @Before("pointCutMethod() || testAOP()") public void doBefore(JoinPoint joinPoint) { System.out.println("@Before:開始添加--order=3"); } //聲明后置通知 ,如果result的類型與proceed執行的方法返回的參數類型不匹配那么就不會執行這個方法 @AfterReturning(pointcut = "pointCutMethod() || testAOP()", returning = "result") public void doAfterReturning(String result) { System.out.println("@AfterReturning:后置通知--order=3"); System.out.println("---" + result + "---"); } //聲明例外通知 @AfterThrowing(pointcut = "pointCutMethod() || testAOP()", throwing = "e") public void doAfterThrowing(Exception e) { System.out.println("@AfterThrowing:例外通知--order=3"); System.out.println(e.getMessage()); } //聲明最終通知 @After("pointCutMethod() || testAOP()") public void doAfter() { System.out.println("@After:最終通知--order=3"); } /* * 聲明環繞通知 * 參數必須是ProceedingJoinPoint,通過該對象的proceed()方法來執行目標函數, * proceed()的返回值就是環繞通知的返回值,proceedingJoinPoint是個接口, * implement JoinPoint,所以也可以獲得目標函數的類名,方法名等參數。 */ @Around("pointCutMethod() || testAOP()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println("@Around:進入方法---環繞通知--order=3"); Object o = pjp.proceed(); System.out.println("@Around:退出方法---環繞通知--order=3"); return o; } }
上面是我寫的一個例子,結合例子我們來看看這些核心的概念:
2.1、切面(Aspect):是一個類,里面定義了通知與切點。
2.2、切點(PointCut):表達式。就是告訴程序要在執行哪些核心業務的時候,執行非核心的業務。
2.3、通知(advice):五種通知方式:
@Before
:前置通知,在調用目標方法之前執行通知定義的任務@After
:后置通知,在目標方法執行結束后,無論執行結果如何都執行通知定義的任務@After-returning
:后置通知,在目標方法執行結束后,如果執行成功,則執行通知定義的任務@After-throwing
:異常通知,如果目標方法執行過程中拋出異常,則執行通知定義的任務@Around
:環繞通知,在目標方法執行前和執行后,都需要執行通知定義的任務。
五種通知方式的執行順序:
正常情況下的執行順序:
@Around:進入方法---環繞通知--order=3 @Before:開始添加--order=3 ============執行業務方法findUser,查找的用戶是:張三============= @Around:退出方法---環繞通知--order=3 @After:最終通知--order=3 @AfterReturning:后置通知--order=3 ---張三---
異常情況下的執行順序: @Around:進入方法---環繞通知--order=3 @Before:開始添加--order=3 ============執行業務方法addUser============= @After:最終通知--order=3 @AfterThrowing:例外通知--order=3 null
三:切點表達式。
這個表達式有很多種,如方法簽名表達式,類型簽名表達式,還有其他的表達式,我只用過前面兩個,其他的沒用過,也不做介紹,如果這兩種表達式不能解決問題的可以參考我參考的博客。
execution(<修飾符模式>?<返回類型模式><方法所在類的完全限定名稱模式>(<參數模式>)<異常模式>?) execution(modifiers-pattern? ret-type-pattern fully-qualified-class-name (param-pattern) throws-pattern?)
public String springMVCmybatic.com.my.aop.UserServiceImp(String a, int b) throw Exception{ }
- modifier-pattern?:表示方法的修飾符,可有可無;對應的就是 public
- ret-type-pattern:表示方法的返回值;對應的就是 String
- fully-qualified-class-name 方法所在類的完全限定名稱;對應的就是 springMVCmybatic.com.my.aop.UserServiceImp
- param-pattern:表示方法的參數;對應的就是 String a, int b
- throws-pattern:表示方法拋出的異常,可有可無;對應的就是 throw Exception
3.2:&&,||,!
@Around("pointCutMethod() || testAOP()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println("@Around:進入方法---環繞通知"); Object o = pjp.proceed(); System.out.println("@Around:退出方法---環繞通知"); return o; }
表達式之間可以采用與,或,非的方式來過濾。
四:多個切點的執行順序
上面的例子中,定義了order=3,重新創建一個切面,定義order=6,執行的結果是:
@Around:進入方法---環繞通知--order=3 @Before:開始添加--order=3 @Around:進入方法---環繞通知--order=6 @Before:開始添加--order=6 ============執行業務方法findUser,查找的用戶是:張三============= @Around:退出方法---環繞通知--order=6 @After:最終通知--order=6 @AfterReturning:后置通知--order=6 ---張三--- @Around:退出方法---環繞通知--order=3 @After:最終通知--order=3 @AfterReturning:后置通知--order=3 ---張三--- @Around:進入方法---環繞通知--order=3 @Before:開始添加--order=3 @Around:進入方法---環繞通知--order=6 @Before:開始添加--order=6 ============執行業務方法addUser============= @After:最終通知--order=6 @AfterThrowing:例外通知--order=6 null @After:最終通知--order=3 @AfterThrowing:例外通知--order=3 null
從結果中可以看出order越小越先執行,執行完了之后就order越小就越后推出。總結為下面的圖:
【參考博客】
1、http://blog.csdn.net/javazejian/article/details/56267036/
2、http://blog.csdn.net/qqxhwwqwq/article/details/51678595