天天看點

Spring筆記——AOP(注解方式)

在Java EE應用中,常常通過AOP來處理一些具有橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等。

1.AOP的基本概念

AOP從程式運作角度考慮程式的流程,提取業務處理過程的切面。AOP面向的是程式運作中各個步驟,希望以更好的方式來組合業務處理的步驟。

AOP架構并不與特定的代碼耦合,AOP架構能處理程式中特定切入點(Pointcut),而不與具體某個具體類耦合。

AOP架構具有如下兩個特征:

☞ 各步驟之間良好隔離性

☞ 源代碼無關性

下面是關于面向切面程式設計的一些術語:

☞ 切面(AspectJ):業務流程運作的某個特定步驟,也就是應用運作過程的關注點,關注點可能橫切多個對象,是以常常也稱為橫切關注點

☞ 連接配接點(Joinpoint):程式執行過程中明确的點,如方法的調用,或者異常的抛出。Spring AOP中,連接配接點總是方法的調用

☞ 增強處理(Advice):AOP架構在特定的切入點執行的增強處理。處理有“around”、“before”和“after”等類型

☞ 切入點(Pointcut):可以插入增強處理的連接配接點。簡而言之,當某個連接配接點滿足指定要求時,該連接配接點将被添加增強處理,該連接配接點也就變成了切入點。Spring預設使用AspectJ切入點文法

☞ 引入:将方法或字段添加到被處理的類中。Spring允許引入新的接口到任何被處理的對象。例如,你可以使用一個引入,使任何對象實作IsModified接口,以此來簡化緩存

☞ 目标對象:被AOP架構進行增強處理的對象,也被稱為被增強的對象

☞ AOP代理:AOP架構建立的對象,簡單的說,代理就是對目标對象的加強。Spring中的AOP代理可以是JDK動态代理,也可以是CGLIB代理

☞ 織入(Weaving):将增強處理添加到目标對象中,并建立一個被增強的對象(AOP代理)的過程就是織入。織入有兩種實作方式:編譯時增強(例如AspectJ)和運作時增強(例如CGLIB)。Spring和其他純Java AOP架構一樣,在運作時完成織入。

由前面的介紹知道:AOP代理其實是由AOP架構動态生成的一個對象,該對象可作為目标對象使用。AOP代理包含了目标對象的全部方法,但AOP代理中的方法與目标對象的方法存在差異:AOP方法在特定切入點添加了增強處理,并回調了目标對象的方法

2.Spring的AOP支援

Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關系也有IOC容器負責管理。是以,AOP代理可以直接使用容器中的其他Bean執行個體作為目标,這種關系可由IOC容器的依賴注入提供。Spring預設使用Java動态代理來建立AOP代理,這樣就可以為任何接口執行個體建立代理了。

Spring也可以使用CGLIB代理,在需要代理類而不是代理接口的時候,Spring自動會切換為使用CGLIB代理。

Spring目前僅支援将方法調用作為連接配接點(Joinpoint),如果需要把對Field的通路和更新也作為增強處理的連接配接點,則可以考慮使用AspectJ。

縱觀AOP程式設計,其中需要程式員參與的隻有三個部分:

☞ 定義普通業務元件

☞ 定義切入點,一個切入點可能橫切多個業務元件

☞ 定義增強處理,增強處理就是在AOP架構為普通業務元件織入的處理動作

3.基于Annotation的“零配置”方式

為了啟用Spring對@AspectJ切面配置的支援,并保證Spring容器中的目标Bean被一個或多個切面自動增強,必須在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:mvc="http://www.springframework.org/schema/mvc"
    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.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- 啟動AspectJ支援 -->
    <aop:aspectj-autoproxy/>
</beans>
           

如果不打算使用Spring的XML Schema配置方式,則應該在Spring配置檔案中增加如下片段來啟用@AspectJ支援。

<!-- 啟動AspectJ支援 -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
           

為了在Spring應用中啟動@AspectJ支援,還需要在應用的類加載路徑中增加兩個AspectJ庫:aspectjweaver.jar和aspectjrt.jar

①定義切面

當啟動了@AspectJ支援後,隻要我們在Spring容器中配置一個帶@Aspect注釋的Bean,Spring将會自動識别該Bean,并将該Bean作為切面處理。

使用@Aspect标注一個Java類,該Java類将會作為切面Bean,如下面的代碼片段所示。

@Aspect
public class LogAspect {
    // 定義該類的其他内容

}
           

切面類(用@Aspect修飾的類)和其他類一樣可以有方法、屬性定義,還可能包含切入點、增強處理定義。

當我們使用@Aspect來修飾一個Java類之後,Spring将不會把該Bean當做元件Bean處理,是以負責自動增強的後處理Bean将會略過該Bean,不會對該Bean進行任何增強處理。

②定義Before曾強處理

當我們在一個切面類裡使用@Before來标注一個方法時,該方法将作為Before增強處理。使用@Before标注時,通常需要指定一個value屬性,該屬性值指定一個切入點表達式,用于指定該增強處理将被織入哪些切入點。例如:

@Component
@Aspect
public class BeforeAdviceTest {

    // 比對org.crazyit.app.service.impl包下所有類的所有方法的執行作為切入點
    @Before("execution(* org.crazyit.app.service.impl.*.*(..))")
    public void authority() {
        System.out.println("模拟執行權限檢查");
    }
}
           
<?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:mvc="http://www.springframework.org/schema/mvc"
    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.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- 自動搜尋元件 -->
    <context:component-scan base-package="main.java.service.impl,main.java.aspect"/>
    <!-- 啟動AspectJ支援 -->
    <aop:aspectj-autoproxy />
</beans>
           

注:需要在加入aopalliance.jar

③ 定義AfterReturning增強處理

類似于使用@Before注解可标注Before增強處理,使用@AfterReturning來标注一個AfterReturning增強處理,AfterReturning增強處理将在目标方法正常完成後被織入

使用@AfterReturning注解時可指定如下兩個常用屬性:

☞ pointcut/value:這兩個屬性的作用是一樣的,他們都用于指定該切入點對應的切入表達式。當指定了pointcut屬性值後,value屬性值将會被覆寫

☞ returning:指定一個傳回值形參名,增強處理定義的方法可通過該形參名來通路目标方法的傳回值

@Component
@Aspect
public class AfterReturningAdviceTest {

    @AfterReturning(returning = "rvt", pointcut = "execution(* main.java.service.*.*(..))")
    public void log(Object rvt) {
        System.out.println("擷取目标方法傳回值:" + rvt);
        System.out.println("模拟記錄日志功能...");
    }
}
           

正如上面的程式中看到的,程式中使用@AfterReturning時,指定了一個returning屬性,該屬性值為rvt,這表明允許在增強處理方法(log方法)中使用名為rvt的形參,該形參代表目标方法的傳回值。

④ 定義AfterThrowing增強處理

使用@AfterThrowing注解可用于标注一個AfterThrowing增強處理,AfterThrowing增強處理主要用于處理程式中未處理的異常。

使用@AfterThrowing時,可指定如下兩個常用屬性:

☞ pointcut/value:這兩個屬性的作用是一樣的,他們都用于指定該切入點對應的切入表達式。當指定了pointcut屬性值後,value屬性值将會被覆寫

☞ returning:指定一個傳回值形參名,增強處理定義的方法可通過該形參名來通路目标方法中所抛出的異常對象

@Component
@Aspect
public class AfterThrowingAdviceTest {

    @AfterThrowing(throwing = "ex", pointcut = "execution(* main.java.service.*.*(..))")
    public void doRecoveryActions(Throwable ex) {
        System.out.println("目标方法中抛出的異常:" + ex);
        System.out.println("模拟抛出異常後的增強處理...");
    }
}
           

正如上面的程式中看到的,程式中使用 @AfterThrowing時,指定了一個throwing屬性,該屬性這為ex,這允許在增強處理方法中使用名為ex的形參,該形參代表目标方法所抛出的異常

@Component
public class Chinese implements Person {

    @Override
    public String sayHello(String name) {

        try {
            System.out.println("sayHello方法開始被執行...");
            new java.io.FileInputStream("a.txt");
        } catch (Exception ex) {
            System.out.println("目标類的異常處理" + ex.getMessage());
        }
        return name + " Hello, Spring AOP";
    }

    public void eat(String food) {
        int a =  / ;
        System.out.println("我正在吃:" + food);
    }
}
           

上面的程式中的sayHello()和eat()兩個方法都會抛出異常,但sayHello()方法中異常将由該方法顯示捕捉,是以Spring AOP不會處理該異常;而eat()方法将抛出一個AirthmeticException異常,且該異常沒有被任何程式所處理,故Spring AOP會對該異常進行處理。

模拟執行權限檢查...
sayHello方法開始被執行...
目标類的異常處理a.txt (系統找不到指定的檔案。)
擷取目标方法傳回值:qiuxiao Hello, Spring AOP
模拟記錄日志功能...
qiuxiao Hello, Spring AOP
模拟執行權限檢查...
目标方法中抛出的異常:java.lang.ArithmeticException: / by zero
模拟抛出異常後的增強處理...
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at main.java.service.impl.Chinese.eat(Chinese.java:)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:)
    at com.sun.proxy.$Proxy8.eat(Unknown Source)
    at main.test.Test.main(Test.java:)
           

注意:使用throwing屬性還有一個額外的作用:它可用于限定切入點隻比對指定類型的異常——加入在上面的doRecoveryActions()方法中定義了ex形參的類型是NullPointerException,則該切入點隻比對抛出NullPointerException異常的情況。上面doRecoveryActions()方法的ex形參類型是Throwable,這表明該切入點可比對抛出任何異常的情況

AOP的AfterThrowing處理雖然可以對目标方法的異常進行處理,但是這種處理與直接使用catch捕捉不同:catch捕捉意味着完全處理該異常,如果catch塊中沒有重新抛出新異常,則該方法可以正常結束;而AfterThrowing處理雖然處理了該異常,但它不能完全處理該異常,該異常依然會傳播到上一級調用者

⑤After增強處理

Spring還提供了一個After增強處理,它與AfterReturning增強處理有點相似,但也有差別:

☞ AfterReturning增強處理隻有在目标方法成功完成後才會被織入

☞ After增強處理不管目标方法如何限制(包括成功完成和遇到異常中止兩種情況),它都會被織入

因為不論一個方法是如何結束的,After增強處理都會被織入,是以After增強處理必須準備處理正常傳回和異常傳回兩種情況,這種增強處理通常用于釋放資源。

使用@After時,需要指定一個value屬性,該屬性值用于指定該增強處理被織入的切入點

@Component
@Aspect
public class AfterAdviceTest {

    @After(value = "execution(* main.java.service.*.*(..))")
    public void release() {
        System.out.println("模拟方法結束後的釋放資源...");
    }
}
           

⑥Around增強處理

@Around用于标注Around增強處理,Around曾強處理是功能比較強大的增強處理,它近似于Before和AfterReturning增強處理的總和,Around增強處理既可在執行目标方法前織入增強動作,也可在執行目标方法之後織入增強動作。

與Before、AfterReturning增強處理不同的是,Around增強處理甚至可以決定目标方法在什麼時候執行,如何執行,甚至可以完全阻止目标方法的執行。

Around增強處理可以改變執行目标方法的參數值,也可改變執行目标方法之後的傳回值。

Around增強處理的功能雖然強大,但通常需要線上程安全的環境下使用。如果需要目标方法執行之前和之後共享某種狀态資料,則應該考慮使用Around增強處理;尤其是需要使用增強處理阻止目标的執行,或需要改變目标方法傳回值時,則隻能使用Around增強處理了。

使用@Around時需要指定一個value屬性,該屬性指定增強處理被織入的切入點。

當定義一個Around增強處理方法時,該方法的第一個參數必須是ProceedingJoinPoint類型(至少包含一個形參),在增強處理方法體内,調用ProceedingJoinPoint的proceed()方法才會執行目标方法——這就是Around增強處理可以完全控制目标方法執行時機、如何執行的關鍵;如果程式沒有調用ProceedingJoinPoint的proceed()方法,則目标方法不會被執行。

調用ProceedingJoinPoint的proceed()方法時,還可以傳入一個Object[]對象,該數組中的值将被傳入目标方法作為執行方法的實參。

@Component
@Aspect
public class AroundAdviceTest {

    @Around(value = "execution(* main.java.service.*.*(..))")
    public Object processTx(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("執行目标方法之前,模拟開始事物...");
        // 執行目标方法,并儲存目标方法執行後的回值
        Object rvt = jp.proceed(new String[] { "被改變的參數" });
        System.out.println("執行目标方法之後,模拟結束事物...");
        return rvt + "新增的内容";
    }
}
           
執行目标方法之前,模拟開始事物...
模拟執行權限檢查...
sayHello方法開始被執行...
目标類的異常處理a.txt (系統找不到指定的檔案。)
執行目标方法之後,模拟結束事物...
擷取目标方法傳回值:被改變的參數 Hello, Spring AOP新增的内容
模拟記錄日志功能...
模拟方法結束後的釋放資源...
被改變的參數 Hello, Spring AOP新增的内容
執行目标方法之前,模拟開始事物...
模拟執行權限檢查...
我正在吃:被改變的參數
目标方法中抛出的異常:java.lang.ArithmeticException: / by zero
模拟抛出異常後的增強處理...
模拟方法結束後的釋放資源...
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at main.java.service.impl.Chinese.eat(Chinese.java:)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:)
    at main.java.aspect.AroundAdviceTest.processTx(AroundAdviceTest.java:)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:)
    at com.sun.proxy.$Proxy10.eat(Unknown Source)
    at main.test.Test.main(Test.java:)
           

當調用ProceedingJoinPoint的proceed()方法時,傳入的Object[]參數值将作為目标方法的參數,如果傳入的Object[]數組長度與目标方法所需要參數的個數不相等,或者Object[]數組元素與目标方法所需參數的類型不比對,程式就會出現異常。

為了能擷取目标方法的參數的個數和類型,需要增強處理方法能通路執行目标方法的參數了。

⑦通路目标方法的參數

通路目标方法最簡單的做法是定義增強處理方法時将第一個參數定義為JoinPoint類型,當該增強處理方法被調用時,該JoinPoint參數就代表了織入增強處理的連接配接點。JoinPoint裡包含了如下幾個常用方法:

☞ Object[] getArgs():傳回執行目标方法時的參數

☞ Signature getSignature():傳回被增強的方法的相關資訊

☞ Object getTarget():傳回被織入增強處理的目标對象

☞ Object[] getThis:傳回AOP架構為目标對象生成的代理對象

通過使用這些方法就可以通路到目标方法的相關資訊

提示:ProceedingJoinPoint 是JoinPoint的子類型

@Component
@Aspect
public class FourAdviceTest {

    // 定義Around增強處理
    @Around("execution(* main.java.service.*.*(..))")
    public Object processTx(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("Around增強:執行目标方法之前,模拟開始事物...");
        // 通路執行目标方法的參數
        Object[] args = jp.getArgs();
        // 當執行目标方法的參數存在
        // 且第一個參數是字元串參數
        if (args != null && args.length > 
                && args[].getClass() == String.class) {
            // 改變第一個目标方法的第一個參數
            args[] = "被改變的參數";
        }
        // 執行目标方法,并儲存目标方法執行後的傳回值
        Object rvt = jp.proceed(args);
        System.out.println("Around增強:執行目标方法之前,模拟結束事物...");

        return rvt + "新曾的内容";
    }

    // 定義Bfore增強處理
    @Before("execution(* main.java.service.*.*(..))")
    public void authority(JoinPoint jp) {
        System.out.println("Before增強:模拟執行權限檢查");
        // 傳回被織入增強處理的目标方法
        System.out.println("Before增強處理:被織入增強處理的目标方法為:"
                + jp.getSignature().getName());

        // 通路執行目标方法的參數
        System.out
                .println("Before增強:目标方法的參數為:" + Arrays.toString(jp.getArgs()));
        System.out.println("Before增強:被織入增強處理的目标對象為:" + jp.getTarget());
    }

    // 定義AfterReturning增強處理
    @AfterReturning(pointcut = "execution(* main.java.service.*.*(..))", returning = "rvt")
    public void log(JoinPoint jPoint, Object rvt) {
        System.out.println("AfterReturning增強:擷取目标方法傳回值:" + rvt);

        System.out.println("AfterReturning增強:模拟日志記錄功能...");

        // 傳回被織入增強處理的目标方法
        System.out.println("AfterReturning增強:被織入增強處理的目标方法為:"
                + jPoint.getSignature().getName());

        // 通路執行目标方法的參數
        System.out.println("AfterReturning增強:目标方法的參數為:"
                + Arrays.toString(jPoint.getArgs()));

        // 通路被增強處理的目标對象
        System.out.println("AfterReturning增強:被織入增強處理的目标對象為:"
                + jPoint.getTarget());
    }

    @After("execution(* main.java.service.*.*(..))")
    public void release(JoinPoint jp) {
        System.out.println("After增強:模拟方法結束後的釋放資源...");
        // 被傳回織入增強處理的目标方法
        System.out.println("After增強:被織入增強處理的目标方法為:"
                + jp.getSignature().getName());

        // 通路執行目标方法的參數
        System.out.println("After增強:目标方法的參數為:" + Arrays.toString(jp.getArgs()));

        // 通路被增強處理的目标對象
        System.out.println("After增強:被織入增強處理的目标對象為:" + jp.getTarget());
    }
}
           

從上面的代碼可以看出,在Before、Around、AfterReturning、After4種增強進行中,其實都可以通過相同的代碼來通路被增強的目标對象、目标方法和方法的參數,但隻有Around增強處理可以改變方法參數。

Before、Around、AfterReturning、After的優先級順序如下:

Spring筆記——AOP(注解方式)

當不同的切面裡的兩個增強處理需要在同一個連接配接點被織入時,Spring AOP将以随機的順序來織入這兩個增強處理。如果應用需要指定不同切面類裡增強處理的優先級,Spring提供了如下兩種解決方案:

☞ 讓切面類實作org.springframework.core.Ordered接口,實作該接口隻需實作一個int getOrder()方法,該方法傳回值越小,則優先級越高

☞ 直接使用@Order注解來修飾一個切面類,使用@Order時可以指定一個int型的value屬性,該屬性值越小,則優先級越高

注:優先級高的切面類裡的增強處理的優先級總是比優先級低的切面類裡的增強處理的優先級更高

同一個切面類裡的兩個相同類型的增強處理在同一個連接配接點被織入時,Spring AOP将以随機的順序來織入這兩個增強處理,沒有辦法指定他們的織入順序。

如果隻要通路目标方法的參數,Spring還提供了一種更簡單的方法:我們可以在程式中使用args來綁定目标方法的參數。如果在一個args表達式中指定了一個或多個參數,則該切入點将隻比對具有對應形參的方法,且目标方法的參數值将被傳入增強處理方法。

@Component
@Aspect
public class AccessArgAspect {

    // 下面的args(food,time)保證該切入點隻比對具有第一個參數是字元串,第二個數是 Date的方法
    @AfterReturning(pointcut = "execution(* main.java.service.*.*(..)) && args(food,time)", returning = "retVal")
    public void access(Date time, String food, Object retVal) {
        System.out.println("目标方法中String參數為:" + food);
        System.out.println("目标方法中Date參數為:" + time);
        System.out.println("模拟記錄日志....");
    }
}
           

上面的程式中增加了&&args(food, time)部分,這意味着可以在增強處理方法中定義food、time兩個形參——定義這兩個形參時,形參類型可以任意指定,但一旦指定了這兩個形參類型,則兩個形參類型将用于限制該切入點隻比對第一個參數類型是String、第二個參數類型是Date的方法。

@Component
public class Chinese implements Person {

    @Override
    public String sayHello(String name) {
        return name + " Hello, Spring AOP";
    }

    public void eat(String food, Date time) {
        System.out.println("我正在吃:" + food + ",現在時間是:" + time);
    }
}
           

上面的類中包含兩個方法,但隻有eat()方法第一個參數類型是String、第二個參數類型是Date,是以Spring AOP将隻對該方法織入access()增強處理。

輸出結果為

Spring筆記——AOP(注解方式)

由結果可以看出,使用args表達式有如下兩個作用:

☞ 提供了一種簡單的方式來通路目标方法的參數

☞ 可用于對切入表達式增加額外的限制

除此之外,使用args表達式時還可以使用如下形式:args(name, age, ..),這表明增強處理方法中可通過name、age來通路目标方法的參數。注意表達式括号中的2個點,它表示可比對更多參數——如果該args表達式對應的增強處理方法簽名為:

@AfterReturning(pointcut = "execution(* main.java.service.*.*(..)) && args(name, age, ..)", returning = "retVal")
public void doSomething(String name, int age)
           

這意味着隻要目标方法第一個參數是String類型、第二個參數是int類型,則該方法就可以比對該切入點。

⑧定義切入點

所謂定義切入點,其實質就是為一個切入點表達式起一個名稱,進而允許在多個增強進行中重用該名稱。

Spring AOP隻支援以Spring Bean的方法執行組作為連接配接點,是以可以把切入點看成所有能和切入點表達式比對的Bean方法。

切入點定義包含兩個部分:

☞ 一個切入點表達式

☞ 一個包含名字和任意參數的方法簽名

其中切入點表達式用于指定該切入點和哪些方法進行比對,包含名字和任意參數的方法簽名将作為該切入點的名稱

在@AspectJ風格的AOP中,切入點簽名采用一個普通的方法定義(方法體通常為空)來提供,且該方法的傳回值必須為void;切入點表達式需使用@Pointcut來标注。

下面的代碼片段定義了一個切入點:anyOldTransfer,這個切入點将比對任何名為transfer的方法執行。

@Pointcut("execution(* main.java.service.*.*(..))")
private void anyOldTransfer() {}
           

如果需要使用本切面類中的切入點,則可在使用注解時,指定value屬性值為已有的切入點,如下面的代碼片段所示:

@AfrerReturning(pointcut="myPointcut()", returning="retVal")
public void writeLog(String msg, Object retVal){
    ......
}
           

⑨切入點訓示符

前面定義切入點表達式時大量使用了execution表達式,其中execution就是一個切入點訓示符。Spring AOP僅支援部分AspectJ的切入點訓示符,但Spring AOP還額外支援一個bean切入點訓示符。

不僅如此,因為Spring AOP隻支援使用方法調用作為連接配接點,是以Spring AOP的切入點訓示符僅比對方法執行的連接配接點。

Spring AOP一共支援如下幾種切入點訓示符:

☞ execution:用于比對執行方法的連接配接點,這是Spring AOP中最主要的切入點訓示符。該切入點的用法也相對複雜,execution表達式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

上面格式中execution是不變的,用于作為execution表達式的開頭,整個表達式中各部分的解釋如下。

♦ modifiers-pattern:指定方法的修飾符,支援通配符,該部分可省略

♦ ret-type-pattern:指定方法傳回值類型,支援通配符,可以使用“*”通配符來比對所有傳回值類型

♦ declaring-type-pattern:指定方法所屬的類,支援通配符,該部分可省略

♦ name-pattern:指定比對指定方法名,支援通配符,可以使用“*”通配符來比對所有方法

♦ param-pattern:指定方法聲明中的形參清單,支援兩個通配符:“”和“..”,其中“”代表一個任意類型的參數,而“..”代表零個或多個任意類型的參數。例如,()比對了一個不接受任何參數的方法,而(..)比對了一個接受任意數量參數的方法(零個或更多),()比對了一個接受一個任何類型參數的方法。(, String)比對了一個接受兩個參數的方法,第一個可以是任意類型,第二個則必須是String類型

♦ throws-pattern:指定方法聲明抛出的異常,支援通配符,該部分可省略

☞ within:限定比對特定類型的連接配接點,當使用Spring AOP的時候,隻能比對方法執行的連接配接點

☞ this:用于限定AOP代理必須是指定類型的執行個體,用于比對該對象的所有連接配接點。當使用Spring AOP的時候,隻能比對方法執行的連接配接點

☞ target:用于限定目标對象必須是指定類型的執行個體,用于比對該對象的所有連接配接點。當使用Spring AOP的時候,隻能比對方法執行的連接配接點

☞ args:用于對連接配接點的參數類型進行限制,要求參數類型是指定類型的執行個體。當使用Spring AOP的時候,隻能比對方法執行的連接配接點

☞ bean:用于指定隻比對指定Bean執行個體内的連接配接點,實際上隻能使用方法執行作為連接配接點。定義Bean表達式時需要傳入Bean的id或name,表示隻比對該Bean執行個體内的連接配接點。支援使用“*”通配符

⑩組合切入點表達式

Spring支援使用如上三個邏輯運算符來組合切入點表達式。

☞ &&:要求連接配接點同時比對兩個切入點表達式

☞ ||:隻要連接配接點比對任意一個切入點表達式

☞ !:要求連接配接點不比對指定指定切入點表達式

繼續閱讀