spring之AOP基本概念和配置詳解

葛倫兒發表於2017-10-24

 首先我們來看一下官方文件所給我們的關於AOP的一些概念性詞語的解釋:

切面(Aspect):一個關注點的模組化,這個關注點可能會橫切多個物件。事務管理是J2EE應用中一個關於橫切關注點的很好的例子。在Spring AOP中,切面可以使用基於模式)或者基於Aspect註解方式來實現。通俗點說就是我們加入的切面類(比如log類),可以這麼理解。

連線點(Joinpoint:在程式執行過程中某個特定的點,比如某方法呼叫的時候或者處理異常的時候。在Spring AOP中,一個連線點總是表示一個方法的執行。通俗的說就是加入切點的那個點

通知(Advice:在切面的某個特定的連線點上執行的動作。其中包括了“around”“before”“after”等不同型別的通知(通知的型別將在後面部分進行討論)。許多AOP框架(包括Spring)都是以攔截器做通知模型,並維護一個以連線點為中心的攔截器鏈。

切入點(Pointcut:匹配連線點的斷言。通知和一個切入點表示式關聯,並在滿足這個切入點的連線點上執行(例如,當執行某個特定名稱的方法時)。切入點表示式如何和連線點匹配是AOP的核心:Spring預設使用AspectJ切入點語法。

引入(Introduction:用來給一個型別宣告額外的方法或屬性(也被稱為連線型別宣告(inter-type declaration))。Spring允許引入新的介面(以及一個對應的實現)到任何被代理的物件。例如,你可以使用引入來使一個bean實現IsModified介面,以便簡化快取機制。

目標物件(Target Object: 被一個或者多個切面所通知的物件。也被稱做被通知(advised)物件。 既然Spring AOP是通過執行時代理實現的,這個物件永遠是一個被代理(proxied)物件。

AOP代理(AOP ProxyAOP框架建立的物件,用來實現切面契約(例如通知方法執行等等)。在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。

織入(Weaving:把切面連線到其它的應用程式型別或者物件上,並建立一個被通知的物件。這些可以在編譯時(例如使用AspectJ編譯器),類載入時和執行時完成。Spring和其他純Java AOP框架一樣,在執行時完成織入。



通知型別:

前置通知(Before advice:在某連線點之前執行的通知,但這個通知不能阻止連線點之前的執行流程(除非它丟擲一個異常)。

後置通知(After returning advice:在某連線點正常完成後執行的通知:例如,一個方法沒有丟擲任何異常,正常返回。

異常通知(After throwing advice:在方法丟擲異常退出時執行的通知。

最終通知(After (finally) advice:當某連線點退出的時候執行的通知(不論是正常返回還是異常退出)。

環繞通知(Around Advice):包圍一個連線點的通知,如方法呼叫。這是最強大的一種通知型別。環繞通知可以在方法呼叫前後完成自定義的行為。它也會選擇是否繼續執行連線點或直接返回它自己的返回值或丟擲異常來結束執行。

環繞通知是最常用的通知型別。和AspectJ一樣,Spring提供所有型別的通知,我們推薦你使用盡可能簡單的通知型別來實現需要的功能。例如,如果你只是需要一個方法的返回值來更新快取,最好使用後置通知而不是環繞通知,儘管環繞通知也能完成同樣的事情。用最合適的通知型別可以使得程式設計模型變得簡單,並且能夠避免很多潛在的錯誤。比如,你不需要在JoinPoint上呼叫用於環繞通知的proceed()方法,就不會有呼叫的問題。



spring AOP的實現

      在spring2.5中,常用的AOP實現方式有兩種。第一種是基於xml配置檔案方式的實現,第二種是基於註解方式的實現。接下來,以具體的示例來講解這兩種方式的使用。下面我們要用到的例項是一個註冊,就有使用者名稱和密碼,我們利用AOP來實現在使用者註冊的時候實現在儲存資料之前和之後或者是丟擲異常時,在這些情況下都給他加上日誌。在這裡我們只講解AOP,所以我只把關鍵程式碼貼出來,不相干的就不貼了。

首先我們來看一下業務邏輯service層:

[java] view plain copy
  1. /** 
  2.  * RegisterService的實現類 
  3.  * @author 曹勝歡 */  
  4. public class RegisterServiceImpl implements RegisterService {  
  5.     private  RegisterDao registerDao;  
  6.     public RegisterServiceImpl() {}  
  7.     /** 帶引數的構造方法 */  
  8.     public RegisterServiceImpl(RegisterDao  registerDao){  
  9.         this.registerDao =registerDao;  
  10.     }  
  11.     public void save(String loginname, String password) {  
  12.         registerDao.save(loginname, password);  
  13.         throw new RuntimeException("故意丟擲一個異常。。。。");  
  14.     }  
  15.       /** set方法 */  
  16.     public void setRegisterDao(RegisterDao registerDao) {  
  17.         this.registerDao = registerDao;  
  18. }}  


對於業務系統來說,RegisterServiceImpl類就是目標實現類,它的業務方法,如save()方法的前後或程式碼會出現異常的地方都是AOP的連線點。

 

下面是日誌服務類的程式碼:


[java] view plain copy
  1. /** 
  2.  * 日誌切面類 
  3.  * @author 曹勝歡 
  4.  */  
  5. public class LogAspect {  
  6.     //任何通知方法都可以將第一個引數定義為 org.aspectj.lang.JoinPoint型別   
  7.     public void before(JoinPoint call) {  
  8.         //獲取目標物件對應的類名  
  9.         String className = call.getTarget().getClass().getName();  
  10.         //獲取目標物件上正在執行的方法名  
  11.         String methodName = call.getSignature().getName();  
  12.         System.out.println("前置通知:" + className + "類的" + methodName + "方法開始了");  
  13.     }  
  14.     public void afterReturn() {  
  15.         System.out.println("後置通知:方法正常結束了");  
  16.     }  
  17.     public void after(){  
  18.         System.out.println("最終通知:不管方法有沒有正常執行完成,一定會返回的");  
  19.     }  
  20.     public void afterThrowing() {  
  21.         System.out.println("異常丟擲後通知:方法執行時出異常了");  
  22.     }  
  23.     //用來做環繞通知的方法可以第一個引數定義為org.aspectj.lang.ProceedingJoinPoint型別  
  24.     public Object doAround(ProceedingJoinPoint call) throws Throwable {  
  25.         Object result = null;  
  26.         this.before(call);//相當於前置通知  
  27.         try {  
  28.             result = call.proceed();  
  29.             this.afterReturn(); //相當於後置通知  
  30.         } catch (Throwable e) {  
  31.             this.afterThrowing();  //相當於異常丟擲後通知  
  32.             throw e;  
  33.         }finally{  
  34.             this.after();  //相當於最終通知  
  35.         }  
  36.         return result;  
  37.     }  
  38. }  


     這個類屬於業務服務類,如果用AOP的術語來說,它就是一個切面類,它定義了許多通知。Before()afterReturn()after()afterThrowing()這些方法都是通知。

 

下面我們就來看具體配置,首先來看一下:

<1>.基於xml配置檔案的AOP實現:這種方式在實現AOP時,有4個步驟。

 

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.         xmlns:aop="http://www.springframework.org/schema/aop"  
  5.         xsi:schemaLocation="  
  6.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  7.             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd>  
  8.     <bean id="registerDaoImpl" class="com.zxf.dao.RegisterDaoImpl"/>  
  9.     <bean id="registerService" class="com.zxf.service.RegisterServiceImpl">  
  10.         <property name=" registerDaoImpl " ref=" RegisterDaoImpl "/>  
  11.     </bean>  
  12.     <!-- 日誌切面類 -->  
  13.     <bean id="logAspectBean" class="com.zxf.aspect.LogAspect"/>  
  14.     <!-- 第1步: AOP的配置 -->  
  15.     <aop:config>  
  16.         <!-- 第2步:配置一個切面 -->  
  17.         <aop:aspect id="logAspect" ref="logAspectBean">  
  18.             <!-- 第3步:定義切入點,指定切入點表示式 -->  
  19.             <aop:pointcut id="allMethod"   
  20.                 expression="execution(* com.zxf.service.*.*(..))"/>   
  21.             <!-- 第4步:應用前置通知 -->  
  22.             <aop:before method="before" pointcut-ref="allMethod" />  
  23.             <!-- 第4步:應用後置通知 -->  
  24.             <aop:after-returning method="afterReturn" pointcut-ref="allMethod"/>  
  25.             <!-- 第4步:應用最終通知 -->  
  26.             <aop:after method="after" pointcut-ref="allMethod"/>  
  27.             <!-- 第4步:應用丟擲異常後通知 -->  
  28.             <aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/>  
  29.             <!-- 第4步:應用環繞通知 -->  
  30.             <!--  
  31.             <aop:around method="doAround" pointcut-ref="allMethod" /> 
  32.              -->  
  33.         </aop:aspect>  
  34.     </aop:config>  
  35. </beans>  

    上述配置針對切入點應用了前置、後置、最終,以及丟擲異常後通知。這樣在測試執行RegisterServiceImpl類的save()方法時,控制檯會有如下結果輸出:

 

前置通知:com.zxf.service.RegisterServiceImpl類的save方法開始了。

針對MySQL的RegisterDao實現中的save()方法。

後置通知:方法正常結束了。

最終通知:不管方法有沒有正常執行完成,一定會返回的。

下面我們在來看一下第二種配置方式:

<2>基於註解的AOP的實現

 

     首先建立一個用來作為切面的類LogAnnotationAspect,同時把這個類配置在spring的配置檔案中。

        在spring2.0以後引入了JDK5.0的註解Annotation的支援,提供了對AspectJ基於註解的切面的支援,從而 更進一步地簡化AOP的配置。具體的步驟有兩步。

 

Spring的配置檔案是如下的配置:

 

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.         xmlns:aop="http://www.springframework.org/schema/aop"  
  5.         xsi:schemaLocation="  
  6.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  7.             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd>  
  8.     <bean id="registerDao" class="com.zxf.dao.RegisterDaoImpl"/>  
  9.     <bean id="registerService" class="com.zxf.service.RegisterServiceImpl">  
  10.         <property name="registerDao" ref="registerDao"/>  
  11.     </bean>  
  12.     <!-- 把切面類交由Spring容器來管理 -->  
  13.     <bean id="logAspectBean" class="com.zxf.aspect.LogAnnotationAspect"/>  
  14.     <!-- 啟用spring對AspectJ註解的支援 -->  
  15.     <aop:aspectj-autoproxy/>  
  16. </beans>  

 

這是那個切面的類LogAnnotationAspect

[java] view plain copy
  1. /** 
  2.  * 日誌切面類 
  3.  */  
  4. @Aspect  //定義切面類  
  5. public class LogAnnotationAspect {  
  6.     @SuppressWarnings("unused")  
  7.     //定義切入點,提供一個方法,這個方法的名字就是改切入點的id  
  8.     @Pointcut("execution(* com.zxf.service.*.*(..))")  
  9.     private void allMethod(){}  
  10.     //針對指定的切入點表示式選擇的切入點應用前置通知  
  11.     @Before("execution(* com. zxf.service.*.*(..))")  
  12.     public void before(JoinPoint call) {  
  13.         String className = call.getTarget().getClass().getName();  
  14.         String methodName = call.getSignature().getName();  
  15.         System.out.println("【註解-前置通知】:" + className + "類的"   
  16.                 + methodName + "方法開始了");  
  17.     }  
  18.     //訪問命名切入點來應用後置通知  
  19.     @AfterReturning("allMethod()")  
  20.     public void afterReturn() {  
  21.         System.out.println("【註解-後置通知】:方法正常結束了");  
  22.     }  
  23.     //應用最終通知  
  24.     @After("allMethod()")  
  25.     public void after(){  
  26.         System.out.println("【註解-最終通知】:不管方法有沒有正常執行完成,"   
  27.                 + "一定會返回的");  
  28.     }  
  29.     //應用異常丟擲後通知  
  30.     @AfterThrowing("allMethod()")  
  31.     public void afterThrowing() {  
  32.         System.out.println("【註解-異常丟擲後通知】:方法執行時出異常了");  
  33.     }  
  34.     //應用周圍通知  
  35.     //@Around("allMethod()")  
  36.     public Object doAround(ProceedingJoinPoint call) throws Throwable{  
  37.         Object result = null;  
  38.         this.before(call);//相當於前置通知  
  39.         try {  
  40.             result = call.proceed();  
  41.             this.afterReturn(); //相當於後置通知  
  42.         } catch (Throwable e) {  
  43.             this.afterThrowing();  //相當於異常丟擲後通知  
  44.             throw e;  
  45.         }finally{  
  46.             this.after();  //相當於最終通知  
  47.         }  
  48.         return result;  
  49.     }  
  50. }  

 備註:輸出結果和前面的一樣。


相關文章