首先我們來看一下官方文檔所給我們的關於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 Proxy):AOP架構建立的對象,用來實現切面契約(例如通知方法執行等等)。在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層:
/** * RegisterService的實作類別 * @author 曹勝歡 */public class RegisterServiceImpl implements RegisterService { private RegisterDao registerDao; public RegisterServiceImpl() {} /** 帶參數的構造方法 */ public RegisterServiceImpl(RegisterDao registerDao){ this.registerDao =registerDao; } public void save(String loginname, String password) { registerDao.save(loginname, password); throw new RuntimeException("故意拋出一個異常。。。。"); } /** set方法 */ public void setRegisterDao(RegisterDao registerDao) { this.registerDao = registerDao;}}
對於業務系統來說,RegisterServiceImpl類就是目標實作類別,它的業務方法,如save()方法的前後或代碼會出現異常的地方都是AOP的連接點。
下面是Log Service類的代碼:
/** * 日誌切面類 * @author 曹勝歡 */public class LogAspect { //任何通知方法都可以將第一個參數定義為 org.aspectj.lang.JoinPoint類型 public void before(JoinPoint call) { //擷取目標對象對應的類名 String className = call.getTarget().getClass().getName(); //擷取目標對象上正在執行的方法名 String methodName = call.getSignature().getName(); System.out.println("前置通知:" + className + "類的" + methodName + "方法開始了"); } public void afterReturn() { System.out.println("後置通知:方法正常結束了"); } public void after(){ System.out.println("最終通知:不管方法有沒有正常執行完成,一定會返回的"); } public void afterThrowing() { System.out.println("異常拋出後通知:方法執行時出異常了"); } //用來做環繞通知的方法可以第一個參數定義為org.aspectj.lang.ProceedingJoinPoint類型 public Object doAround(ProceedingJoinPoint call) throws Throwable { Object result = null; this.before(call);//相當於前置通知 try { result = call.proceed(); this.afterReturn(); //相當於後置通知 } catch (Throwable e) { this.afterThrowing(); //相當於異常拋出後通知 throw e; }finally{ this.after(); //相當於最終通知 } return result; }}
這個類屬於商務服務類,如果用AOP的術語來說,它就是一個切面類,它定義了許多通知。Before()、afterReturn()、after()和afterThrowing()這些方法都是通知。
下面我們就來看具體配置,首先來看一下:
<1>.基於xml設定檔的AOP實現:這種方式在實現AOP時,有4個步驟。
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd> <bean id="registerDaoImpl" class="com.zxf.dao.RegisterDaoImpl"/> <bean id="registerService" class="com.zxf.service.RegisterServiceImpl"> <property name=" registerDaoImpl " ref=" RegisterDaoImpl "/> </bean> <!-- 日誌切面類 --> <bean id="logAspectBean" class="com.zxf.aspect.LogAspect"/> <!-- 第1步: AOP的配置 --> <aop:config> <!-- 第2步:配置一個切面 --> <aop:aspect id="logAspect" ref="logAspectBean"> <!-- 第3步:定義切入點,指定切入點運算式 --> <aop:pointcut id="allMethod" expression="execution(* com.zxf.service.*.*(..))"/> <!-- 第4步:應用前置通知 --> <aop:before method="before" pointcut-ref="allMethod" /> <!-- 第4步:應用後置通知 --> <aop:after-returning method="afterReturn" pointcut-ref="allMethod"/> <!-- 第4步:應用最終通知 --> <aop:after method="after" pointcut-ref="allMethod"/> <!-- 第4步:應用拋出異常後通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/> <!-- 第4步:應用環繞通知 --> <!-- <aop:around method="doAround" pointcut-ref="allMethod" /> --> </aop:aspect> </aop:config></beans>
上述配置針對切入點應用了前置、後置、最終,以及拋出異常後通知。這樣在測試執行RegisterServiceImpl類的save()方法時,控制台會有如下結果輸出:
前置通知:com.zxf.service.RegisterServiceImpl類的save方法開始了。
針對MySQL的RegisterDao實現中的save()方法。
後置通知:方法正常結束了。
最終通知:不管方法有沒有正常執行完成,一定會返回的。
下面我們在來看一下第二種配置方式:
<2>基於註解的AOP的實現
首先建立一個用來作為切面的類LogAnnotationAspect,同時把這個類配置在spring的設定檔中。
在spring2.0以後引入了JDK5.0的註解Annotation的支援,提供了對AspectJ基於註解的切面的支援,從而 更進一步地簡化AOP的配置。具體的步驟有兩步。
Spring的設定檔是如下的配置:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd> <bean id="registerDao" class="com.zxf.dao.RegisterDaoImpl"/> <bean id="registerService" class="com.zxf.service.RegisterServiceImpl"> <property name="registerDao" ref="registerDao"/> </bean> <!-- 把切面類交由Spring容器來管理 --> <bean id="logAspectBean" class="com.zxf.aspect.LogAnnotationAspect"/> <!-- 啟用spring對AspectJ註解的支援 --> <aop:aspectj-autoproxy/></beans>
這是那個切面的類LogAnnotationAspect
/** * 日誌切面類 */@Aspect //定義切面類public class LogAnnotationAspect { @SuppressWarnings("unused") //定義切入點,提供一個方法,這個方法的名字就是改切入點的id @Pointcut("execution(* com.zxf.service.*.*(..))") private void allMethod(){} //針對指定的切入點運算式選擇的切入點應用前置通知 @Before("execution(* com. zxf.service.*.*(..))") public void before(JoinPoint call) { String className = call.getTarget().getClass().getName(); String methodName = call.getSignature().getName(); System.out.println("【註解-前置通知】:" + className + "類的" + methodName + "方法開始了"); } //訪問命名切入點來應用後置通知 @AfterReturning("allMethod()") public void afterReturn() { System.out.println("【註解-後置通知】:方法正常結束了"); } //應用最終通知 @After("allMethod()") public void after(){ System.out.println("【註解-最終通知】:不管方法有沒有正常執行完成," + "一定會返回的"); } //應用異常拋出後通知 @AfterThrowing("allMethod()") public void afterThrowing() { System.out.println("【註解-異常拋出後通知】:方法執行時出異常了"); } //應用周圍通知 //@Around("allMethod()") public Object doAround(ProceedingJoinPoint call) throws Throwable{ Object result = null; this.before(call);//相當於前置通知 try { result = call.proceed(); this.afterReturn(); //相當於後置通知 } catch (Throwable e) { this.afterThrowing(); //相當於異常拋出後通知 throw e; }finally{ this.after(); //相當於最終通知 } return result; }}
備忘:輸出結果和前面的一樣。