Spring筆記——13.Spring的AOP

AOP能夠橫向地看待程序,將與邏輯無關的功能,比如說日誌,事物等從代碼中抽離出來。


使用AspectJ實現AOP

安裝Aspect,配置好環境變量後,用記事本寫一個類:

public class HelloWorld {
	public void sayHello(){
		System.out.println("Hello AspectJ!");
	}
	public static void main(String args[]){
		HelloWorld h=new HelloWorld();
		h.sayHello();
	}
}
public aspect TxAspect {
	void around():call(void sayHello()){
		System.out.println("Transaction Begin");
		proceed();
		System.out.println("Transaction End");
	}
}

然後使用ajc命令編譯它們。ajc相當於增強的javac,能夠識別裏面的aspect關鍵字。編譯的過程就是自動把around中代碼添加到HelloWorld中特定位置的過程。之後運行主函數就能看到,Transaction的開始與結束方法會在目標對象的Pointcut前後執行。

通過上述例子,我們知道AOP的目的就是保證在不修改原代碼的情況下爲系統中業務組件的多個業務方法以非耦合的方式添加某種通用的功能。我們只需要在外部寫好增強方法,架構會自動地幫我們添加到目標對象中的切入點處。AOP的實現可以分爲動態跟靜態。所謂靜態就像這種,在編譯的時候織如增強代碼,以AspectJ爲代表。而動態AOP實現,就像Spring的AOP一樣,則是在運行階段生成AOP代理。所謂代理就是框架會額外創建一個對象,用以代替目標對象,並且代理也會攜帶需要增強的功能。


AOP的基本概念

切面(Aspect):用於組織多個Advice。Advice放在切面中定義。

連接點(Joinpoint):程序執行過程中的明確的點,Spring中是方法。

增強處理(Advice):AOP框架在特定的切入點需要執行的增強處理,處理有around、after、before等類型。

切入點(Pointcut):可以插入增強處理的點,也就是那些符合特定規則的連接點。

引入:將方法或者字段添加到被處理的類中。Spring允許將新的接口引入到任何被處理的類中。

目標對象:被選定的要被增強的對象。如果AOP框架採用動態AOP,那麼目標對象就是代理對象。

AOP代理:AOP框架額外創建的包含了增強處理與目標對象的新對象。Spring中的AOP可以是JDK動態代理,用於爲實現接口的目標對象的代理;也可以是cglib代理,爲不實現接口的目標對象進行代理。

織入:將增強處理添加到目標對象中,並創建一個被增強的對象的過程。織入可以有編譯時織入,或者運行時織入。


Spring的AOP支持

Spring中的AOP代理由Spring的容器負責生成管理,依賴關係也是由容器負責,所以AOP代理可以直接使用容器中的其它bean實例作爲目標。默認使用動態代理,這樣就可以爲任何接口實例創建代理。當需要代理類而不是接口的時候Spring也會自動切換爲使用cglib代理。但因爲Spring是推薦使用面向接口編程的,因此通常沒機會使用cglib。

Spring AOP使用純java類實現,無需特殊編譯器。僅支持方法作爲連接點。在AOP編程中,我們只需要做如下三件事:

  • 定義普通業務組件

  • 定義切入點

  • 定義增強處理


基於註解的“零配置”方式

Spring使用了和AspectJ 5一樣的註解,但是底層並不是AspectJ編譯器,而是Spring AOP。爲了啓用Spring對@AspectJ的支持,我們需要在xml文件頭增添如下代碼:

xmlns:context="
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<aop:aspectj-autoproxy/>	

此外我們還需要將三個jar包添加到項目中。aspectjweaver.jar和aspectjrt.jar這兩個包在AspectJ的lib下有。另一個aopalliance.jar需要額外下載,用以支持Spring AOP。


定義切面bean

當啓動了@AspectJ支持後,只要爲某一個bean增加@Aspect註解,框架就會把這個bean視爲切面bean而不去實例化它。配置切面bean與普通bean一樣。切面類一樣可以有成員變量,方法,還可能包括切入點,增強處理定義。

@Aspect
public class LogAspect
{
//其它內容
}


定義Before增強處理

在一個切面類中用@Before修飾的方法會在Pointcut之前運行。通常Before需要一個value值,這個值表明了一個切入點表達式,用以指定增強處理被織入哪些切入點。

@Aspect
public class LogAspect
{
    //表示com.cm包下面的所有類的所有方法都是切入點,..表示任意個數、類型不限的形參
    @Before("execution(* com.cm.*.*(..)");
    public void authority()
    {
        System.out.println("模擬執行權限檢查");
    }
        
}

只需如此配置,在com.cm下面的bean執行方法之前,這段增強代碼就會自動執行。如果沒有特殊處理,目標方法隨後就會執行。如果不想目標方法執行,就拋出一個異常。


定義AfterReturning增強處理

這個註解可以定義AfterReturning增強處理,增強代碼將在目標方法執行之後執行。這個註解中需要有兩個屬性。pointcut/value這兩個屬性的作用一樣,都用於指定切入點的表達式。既可以是已經有的切入點,也可以直接定義切入點表達式。當制定了pointcut屬性後,value屬性值將會被覆蓋。returning屬性制定一個形參名,用於表示Advice方法中可定義與此同名的形參,該形參能夠訪問目標方法的反滬指。除此之外,在Advice中定義該形參時指定的類型,會限制目標方法必須返回指定類型的值或者沒有返回值。

@Aspect
public class LogAspect
{
@AfterReturning(returning="rvt",pointcut="execution(* com.cm.*.*(..))")
public void log(Object rvt)
    {
    System.out.println("獲取目標方法返回值"+rvt);
    System.out.println("模擬記錄日誌功能");
    }
}

上述程序註解中的rvt,可以理解爲它就是目標方法的返回值。returning屬性所指定的形參名必須對應增強處理中的一個形參名,當目標方法執行後,返回值作爲相應的參數傳入增強處理方法。我們可以利用這個形參來過濾切入點,只選取那些返回值類型與增強函數中形參類型一致的目標方法。此處的形參類型是Object,那麼所有符合value表達式的方法都可以。如果這裏指定的是String,那麼除了要滿足value表達式,只有那些返回值爲String的方法才能夠作爲目標方法。這裏只能訪問返回值,不能改變它。


定義AfterThrowing增強處理

使用@AfterThrowing註釋可以修飾AfterThrowing增強處理,用於處理程序中未處理的異常。pointcut/value屬性用於指定一個value表達式,與上述相同。throwing屬性用於制定一個形參名,用於表示Advice方法中可定義與此同名的從餐,該形參可用於訪問目標方法拋出的異常。跟上述類似,也會起到限制拋出異常類型的作用。

@Aspect
public class LogAspect
{
@AfterThrowing(throwing="ex",pointcut="execution(* com.cm.*.*(..))")
public void log(Object ex)
    {
    System.out.println("獲取目標方法拋出的異常"+ex);
    System.out.println("模擬對異常的修復");
    }
}

注意只有當目標方法拋出一個未處理的異常時,纔會啓動織入。注意這種機制與catch完全不同。catch能夠完全處理異常,只要catch中沒有新的異常,該方法可以正常結束。而AfterThrowing雖然能處理異常,不過不能完全處理。


After增強處理

AfterReturning只有在目標方法成功完成後纔會被織入,而After增強處理則無論目標方法如何結束,是否成功完成還是遇到了異常終止,增強代碼都會被織入,所以它必須應對正常結束和異常終止兩種情況。

@Aspect
public class LogAspect
{
@After("execution(* com.cm.*.*(..))")
public void log()
    {
    System.out.println("模擬方法結束後的釋放資源");
    }
}


Around增強處理

近似於Before和AfterReturning的總和,可以在執行目標方法之前織入,也可之後織入。Around可以決定目標方法在什麼時候執行,如何執行,甚至可以完全阻止目標方法的執行。Around增強處理可以改變執行目標方法的參數值,可一個改變執行目標方法之後的返回值。不過雖然功能強大,但必須在線程安全下使用。Around需要指定一個value。方法的第一個參數必須爲ProceedingJoinPoint類型。也就是說至少要包含一個形參。調用這個形參的proceed方法纔會執行目標方法。如果沒有顯式調用,目標方法就不會被執行。當使用proceed方法時,還可以傳入一個Object[]對象作爲參數,該數組中的值將被傳入目標方法作爲執行方法的實參。

@Aspect
public class LogAspect
{
@Around("execution(* com.cm.*.*(..))")
public Object log(ProceedingJoinPoint jp)
    throws java.lang.Throwable
    {
    System.out.println("執行目標方法前,模擬事物開始");
    //得到了目標方法的原始參數
    Object[] args=jp.getArgs();
    if(args!=null&&args.length>1)
    {
    args[0]="增加的前綴“+args[0];
    }
    //修改了目標方法的參數
    Object rvt=jp.proceed(args);
    System.out.println("執行目標方法後,模擬事物結束");
    if(rvt!=null&&rvt instanceof Integer)
    {
    //修改了目標方法的返回值
    rvt=(Integer)rvt*(Integer)rvt;
    return rvt;
}


訪問目標方法的參數

最簡單的方法就是定義增強處理方法是降低一個參數定義爲JoinPoint類型的。當增強方法被調用是,這個參數就代表了織入增強處的連接點。需要注意在around標註中使用的是ProceedingJoingPoint,因爲需要這裏面的proceed。除了這點,基本用法沒有區別。JoingPoint裏面有如下四個常用方法:

  • Object[] getArgs():返回執行目標方法時的參數,入上例所示。

  • Signature getSignature():返回被增強的方法的相關信息,也就是Pointcut方法的信息。

  • Object getTarget():返回被植入增強處理的目標對象,也就是包含了被增強方法的對象。

  • Object getThis():返回AOP框架爲目標對象生成的代理對象,也就是包含了增強方法和原方法的超級大方法。

注意當一個切面需要兩個以上不同的增強處理時,默認順序是隨機的,但也可以認爲確定順序,只需讓切面類實現org.springframework.core.Ordered接口,該接口的實現類只需要實現一個int getOrder()方法。該方法的返回值越小,順序越靠前。或者也可以在切面類中使用@Order來註釋,它可以用有一個int類型的value屬性,用於指定順序。數值越小,優先級越高。

除此之外還有一種更簡單的方法來訪問目標方法參數。在切面類定義切入點表達式時後面&&args(arg0,arg1),這樣的話在增強方法中也可以增加兩個任意類型的形參,參數名就是arg0,arg1。一旦確定之後,切入點方法也必須含有這兩個形參類型的參數纔可以被選中,而匹配之後這兩個形參就是目標方法的參數了。實際上這相當於給切入點表達式增加了限制。還記得之前表達式中有(..)這種寫法嗎,這就是說參數不限。而本例中限制了參數。


定義切入點

所謂定義切入點,實際上就是爲一個切入點表達式起一個名字,這樣在不同的切面類中只要使用表達式的名字就可以了。以後如果需要修改表達式,只需要在一個地方修改即可。切入點定義包含兩個部分,一個是切入點表達式,另一個是包含名字和任意參數的方法簽名。前面決定論了哪些方法可以作爲切入點,而後面的則代表了表達式。我們需要使用@Point註解來指明切入點。

@Aspect
public class SystemArchitecture
{
@Pointcut("exection(* transfer(..))")
public void myPointcut(){}
}

只需如此這般,就定義好了一個名爲myPointcut的切入點表達式。使用的時候,我們只需要在註解中使用pointcut屬性,另它的值爲myPointcut即可。


切入點指示符

之前我們在定義切入點表達式時大量使用execution,實際上這就是一個切入點指示符。


組合切入點表達式

我們可以通過&&,||,!來對切入點表達式進行邏輯處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章