SpringAOP的應用實例與總結


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

三:切點表達式。

這個表達式有很多種,如方法簽名表達式,類型簽名表達式,還有其他的表達式,我只用過前面兩個,其他的沒用過,也不做介紹,如果這兩種表達式不能解決問題的可以參考我參考的博客。

3.1:方法簽名表達式
 
execution(<修飾符模式>?<返回類型模式><方法所在類的完全限定名稱模式>(<參數模式>)<異常模式>?)
execution(modifiers-pattern? ret-type-pattern fully-qualified-class-name (param-pattern) throws-pattern?)   
 
其實如果單純的給定這個表達式還是不容易記憶,下面對比方法的定義來記憶,一個java方法的全部定義方式可以表示成下面的方式:
    
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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM