相信只要使用過Spring架構的,大家對於AOP都不陌生,尤其提起它就能立刻隨口說出,一般用在Tlog、異常處理、許可權驗證等方面。但剛開始接觸難免會有各種各樣的疑惑,今天抽時間,按照之前的理解整理了一份關於Spring AOP的簡單教程,希望能夠協助大家儘快的瞭解它的實現過程及原理。首先來明確幾個概念:
系統在運行之前,AOP的功能模組需要織入到OOP的功能模組中。要進行這種織入過程,需要知道在系統的哪些功能點上進行織入操作,這些將要在其上進行織入操作的系統功能點就稱為JointPoint。如某方法調用的時候或者處理異常的時候,在Spring AOP中,一個連接點總是表示一個方法的執行。常見的幾種類型的JoinPoint:
Ø 方法調用:當某個方法被調用的時候所處的程式執行點;
Ø 方法執行:該類型表示的是某個方法內部執行開始時的點,應該與方法調用相區分;
Ø 構造方法調用:程式執行過程中對某個對象調用其構造方法進行初始化時的點;
Ø 構造方法執行:它與構造方法調用關係如同方法調用與方法執行間的關係;
Ø 欄位設定:對象的某個屬性通過setter方法被設定或直接被設定的執行點;
Ø 欄位擷取:某個對象相應屬性被訪問的執行點;
Ø 異常處理執行:某些類型異常拋出後,對應的異常處理邏輯執行點;
Ø 類初始化:類中某些靜態類型或靜態塊的初始化時的執行點。
Pointcut代表的是JoinPoint的表述方式。在將橫切邏輯織入當前系統的過程中,雖然知道需要在哪些功能點上織入AOP的功能模組,但需要一種表達方法。Pointcut和一個切入點運算式關聯,並在滿足這個切入點的Joinpoint上運行。目前通常使用的Pointcut方式有以下幾種:
Ø 直接指定Joinpoint所在的方法名稱;
Ø Regex,Spring的AOP支援該種方式;
Ø 使用特定的Pointcut表述語言,Spring 2.0後支援該方式。
Advice是單一橫切關注點邏輯的載體,它代表將會織入到JoinPoint的橫切邏輯。在切面的某個特定的連接點上執行的邏輯。根據它在Joinpoint位置執行時機的差異或完成功能的不同,可分為以下幾種形式:
Ø Before Advice:在Joinpoint指定位置之前執行的Advice類型,可以採用它來做一些系統的初始化工作,如設定系統初始值,擷取必要系統資源等。
Ø After Advice:在相應連接點之後執行的Advice類型,它還可以細分為以下三種:
² After Returning Advice:只有當前Joinpoint處執行流程正常完成後,它才會執行;
² After throwing Advice:在當前Joinpoint執行過程中拋出異常的情況下會執行;
² After Advice:該類型的Advice不管JoinPoint處執行流程是正常還是拋出異常都會執行。
Ø Around Advice:對附加其上的Joinpoint進行包裹,可以在joinpoint之前和之後都指定相應的邏輯,甚至中斷或忽略joinpoint處原來程式流程的執行。
它是對系統中橫切關注點邏輯進行模組化封裝的AOP概念實體,它可以包含多個Pointcut以及相關的Advice定義。
經過織入過程後,以Aspect模組化的橫切關注點才會整合到oop的現存系統中,而完成織入過程實體稱為織入器。Spring中使用一組類來完成最終的織入操作,ProxyFactory類是Spring AOP最通用的織入器。
符合Pointcut所指定的條件,將在織入過程中被織入橫切邏輯的對象,稱之為目標對象。
單看上述的概念,可能會覺得有點眼花繚亂,其實通過一個簡單的AOP的執行個體即可以協助我們很快的瞭解其內部的機制。其實對於方法攔截有不同的實現方式,常用的即有直接採用Spring提供的各種Advice進行攔截,另一種則是採用MethodInterceptor方式進行攔截。
Spring提供的Advice攔截方式
定義一個邏輯介面IBusinessLogic:
package com.wow.asc.aop;public interface IBusinessLogic { public void foo(); public void bar() throws BusinessLogicException; public long time();}
其中有一個BusinessLogicException異常,它用於後面對於ThrowsAdvice進行檢驗的執行個體,在此定義為:
package com.wow.asc.aop;public class BusinessLogicException extends Exception {}
對於該商務邏輯的實現BusinessLogic,如下所示:
package com.wow.asc.aop;public class BusinessLogic implements IBusinessLogic { @Override public void foo() { System.out.println("Inside BusinessLogic.foo()"); } @Override public void bar() throws BusinessLogicException { System.out.println("Inside BusinessLogic.bar()"); throw new BusinessLogicException(); } /* * 返回該方法執行的時間 */ @Override public long time() { System.out.println("Inside BusinessLogic.time()"); long startTime = System.currentTimeMillis(); for(int i = 0; i < 100000000; i++); long endTime = System.currentTimeMillis(); return (endTime - startTime); }}
在完成上述商務邏輯編碼後,接下來將進行更多的橫切插入點的設計,如在方法執行前或返回時、拋出異常時進行各種處理。對於Advice的寫法如下所示:
package com.wow.asc.aop;import java.lang.reflect.Method;import org.springframework.aop.MethodBeforeAdvice;/** 表示一個在方法執行前進行攔截的一個Advice */public class TracingBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("execute before (by " + method.getDeclaringClass().getName() + "." + method.getName() + ")"); }}package com.wow.asc.aop;import java.lang.reflect.Method;import org.springframework.aop.AfterReturningAdvice;/* * 表示一個在方法返回時進行攔截的Advice*/public class TracingAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(method.getDeclaringClass().getName() + "." + method.getName() + "spend time: " + returnValue); System.out.println("execute after (by " + method.getDeclaringClass().getName() + "." + method.getName() + ")"); }}package com.wow.asc.aop;import java.lang.reflect.Method;import org.springframework.aop.ThrowsAdvice;/* * 表示一個異常拋出時進行攔截的Advice*/public class TracingThrowsAdvice implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object target, Throwable subclass) { System.out.println( "Logging that a " + subclass + "Exception was thrown."); }}
在設計完上述的代碼及邏輯後,即可以通過applicationContext.xml將上述類進行組合,在裝配過程中即可明確哪個類的哪些方法需要被攔截,及攔截前、後會做哪些事情。具體的配置樣本:
<?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"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://localhost:8080/schema/www.springframework.org/schema/context/spring-context-2.5.xsd"><bean id="businessLogic" class="com.wow.asc.aop.BusinessLogic" /><bean id="businessLogicBean" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="proxyInterfaces"><value>com.wow.asc.aop.IBusinessLogic</value></property><property name="target"><ref local="businessLogic"/> </property><property name="interceptorNames"><list><value>theTracingBeforeAdvisor</value> <value>theTracingAfterAdvisor</value> <value>theTracingThrowsAdvisor</value></list></property></bean><bean id="theTracingBeforeAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><property name="advice"> <ref local="theTracingBeforeAdvice"/> </property><property name="pattern"><value>.*</value></property></bean><bean id="theTracingAfterAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><property name="advice"><ref local="theTracingAfterAdvice"/></property><property name="pattern"><value>.*time.*</value></property></bean><bean id="theTracingThrowsAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><property name="advice"><ref local="theTracingThrowsAdvice"/></property><property name="pattern"><value>.*bar.*</value></property></bean><bean id="theTracingBeforeAdvice" class="com.wow.asc.aop.TracingBeforeAdvice"/><bean id="theTracingAfterAdvice" class="com.wow.asc.aop.TracingAfterAdvice"/><bean id="theTracingThrowsAdvice" class="com.wow.asc.aop.TracingThrowsAdvice"/></beans>
通過上述的配置,我們可以看出我們將IBusinessLogic做為代理介面,同時它的真正的目標類是BusinesssLogic。同時會對所有進入方法之前採用TracingBeforeAdvice進行攔截,進行方法前的預先處理;對time方法採用TracingAfterAdvice進行攔截,進行方法返回後的處理;對於bar則採用TracingThrowsAdvice進行攔截,當方法返回BusinessLogicException時進行相應的處理。
在配置完上述類的依賴關係及需要攔截的方法後,即可以編寫用戶端程式來調用,查看它的運行機制。用戶端調用代碼:
package com.wow.asc.test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.wow.asc.aop.BusinessLogicException;import com.wow.asc.aop.IBusinessLogic;public class AOPTest { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); IBusinessLogic ibl = (IBusinessLogic)ac.getBean("businessLogicBean"); ibl.foo(); try { ibl.bar(); } catch (BusinessLogicException e) { System.out.println("Caught BusinessLogicException"); } ibl.time(); }}
通過運行結果來詳細的瞭解下,看是否真正的如上所述,會在方法前、後及異常拋出時能夠攔截並進行相應處理。結果如下:
1、execute before (by com.wow.asc.aop.IBusinessLogic.foo)2、Inside BusinessLogic.foo()3、execute before (by com.wow.asc.aop.IBusinessLogic.bar)4、Inside BusinessLogic.bar()5、Logging that a com.wow.asc.aop.BusinessLogicExceptionException was thrown.6、Caught BusinessLogicException7、execute before (by com.wow.asc.aop.IBusinessLogic.time)8、Inside BusinessLogic.time()9、com.wow.asc.aop.IBusinessLogic.timespend time: 4610、execute after (by com.wow.asc.aop.IBusinessLogic.time)
其實通過1、3、7行可以非常清晰的瞭解到,每個方法在執行前都被TracingBeforeAdvice攔截到,並執行了預先處理。5、6行表示當調用的是bar方法時,會被TracingThrowsAdvice攔截,當有異常拋出時,會執行相應的處理;8、9、10行則表示當調用的是time方法,返回時會被TracingAfterAdvice攔截,對其傳回值進行處理。
MethodInterceptor攔截方式
採用該種方式進行攔截,需要實現一個繼承自MethodInterceptor的類,並將該類註冊至spring Context中,具體如下:
package com.wow.asc.aop;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;public class MyInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) { Object result = null; StringBuffer info = new StringBuffer(); info.append("intercept the method: "); info.append(invocation.getMethod().getDeclaringClass().getName()); info.append("."); info.append(invocation.getMethod().getName()); System.out.println("start " + info.toString()); try { result = invocation.proceed(); } catch (Throwable e) { e.printStackTrace(); } finally { System.out.println("end " + info.toString()); } return result; }}
對於類的裝配,其實和上面的非常類似,樣本:
<bean id="testBean" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="proxyInterfaces"><value>com.wow.asc.aop.IBusinessLogic</value></property><property name="target"><ref local="businessLogic"/> </property><property name="interceptorNames"><list><value>myInterceptor</value></list></property></bean><bean id="myInterceptor" class="com.wow.asc.aop.MyInterceptor"/>
再通過用戶端進行調用,可得到運行結果,從結果來分析可以看出它在方法執行的前、後均添加了相應的日誌。
start intercept the method: com.wow.asc.aop.IBusinessLogic.fooInside BusinessLogic.foo()end intercept the method: com.wow.asc.aop.IBusinessLogic.foostart intercept the method: com.wow.asc.aop.IBusinessLogic.timeInside BusinessLogic.time()end intercept the method: com.wow.asc.aop.IBusinessLogic.time
至此,採用兩種不同方式實現的AOP就結束了,希望大家能夠體會到其中的奧妙。
參考AOP資料:
http://www.zabada.com/technology/aop-example.html
http://onjava.com/pub/a/onjava/2004/07/14/springaop.html?page=1
http://dnizna.iteye.com/blog/1157663