一: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
