Spring AOP 使用介紹媒惕,從前世到今生

轉自:https://javadoop.com/post/spring-aop-intro

Spring AOP 發(fā)展到現(xiàn)在出現(xiàn)的全部 3 種配置方式顶瞳。

由于 Spring 強大的向后兼容性,實際代碼中往往會出現(xiàn)很多配置混雜的情況殉摔,而且居然還能工作,本文希望幫助大家理清楚這些知識记焊。

AOP, AspectJ, Spring AOP

我們先來把它們的概念和關系說說清楚逸月。

AOP 要實現(xiàn)的是在我們原來寫的代碼的基礎上,進行一定的包裝遍膜,如在方法執(zhí)行前碗硬、方法返回后、方法拋出異常后等地方進行一定的攔截處理或者叫增強處理瓢颅。

AOP 的實現(xiàn)并不是因為 Java 提供了什么神奇的鉤子恩尾,可以把方法的幾個生命周期告訴我們,而是我們要實現(xiàn)一個代理挽懦,實際運行的實例其實是生成的代理類的實例翰意。

作為 Java 開發(fā)者,我們都很熟悉 AspectJ 這個詞信柿,甚至于我們提到 AOP 的時候冀偶,想到的往往就是 AspectJ,即使你可能不太懂它是怎么工作的渔嚷。這里进鸠,我們把 AspectJ 和 Spring AOP 做個簡單的對比:

Spring AOP:

  • 它基于動態(tài)代理來實現(xiàn)。默認地形病,如果使用接口的客年,用 JDK 提供的動態(tài)代理實現(xiàn)霞幅,如果沒有接口,使用 CGLIB 實現(xiàn)量瓜。大家一定要明白背后的意思司恳,包括什么時候會不用 JDK 提供的動態(tài)代理,而用 CGLIB 實現(xiàn)绍傲。

  • Spring 3.2 以后扔傅,spring-core 直接就把 CGLIB 和 ASM 的源碼包括進來了,這也是為什么我們不需要顯式引入這兩個依賴

  • Spring 的 IOC 容器和 AOP 都很重要唧取,Spring AOP 需要依賴于 IOC 容器來管理吕粗。

  • 如果你是 web 開發(fā)者浇衬,有些時候,你可能需要的是一個 Filter 或一個 Interceptor洽腺,而不一定是 AOP鹏往。

  • Spring AOP 只能作用于 Spring 容器中的 Bean淡诗,它是使用純粹的 Java 代碼實現(xiàn)的,只能作用于 bean 的方法伊履。

  • Spring 提供了 AspectJ 的支持韩容,后面我們會單獨介紹怎么使用,一般來說我們用純的 Spring AOP 就夠了唐瀑。

  • 很多人會對比 Spring AOP 和 AspectJ 的性能群凶,Spring AOP 是基于代理實現(xiàn)的,在容器啟動的時候需要生成代理實例哄辣,在方法調用上也會增加棧的深度请梢,使得 Spring AOP 的性能不如 AspectJ 那么好。

AspectJ:

  • AspectJ 出身也是名門力穗,來自于 Eclipse 基金會毅弧,link:https://www.eclipse.org/aspectj

  • 屬于靜態(tài)織入,它是通過修改代碼來實現(xiàn)的当窗,它的織入時機可以是:

    • Compile-time weaving:編譯期織入够坐,如類 A 使用 AspectJ 添加了一個屬性,類 B 引用了它崖面,這個場景就需要編譯期的時候就進行織入元咙,否則沒法編譯類 B。
    • Post-compile weaving:也就是已經生成了 .class 文件巫员,或已經打成 jar 包了蛾坯,這種情況我們需要增強處理的話,就要用到編譯后織入疏遏。
    • Load-time weaving:指的是在加載類的時候進行織入脉课,要實現(xiàn)這個時期的織入救军,有幾種常見的方法。1倘零、自定義類加載器來干這個唱遭,這個應該是最容易想到的辦法,在被織入類加載到 JVM 前去對它進行加載呈驶,這樣就可以在加載的時候定義行為了拷泽。2、在 JVM 啟動的時候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar袖瞻。
  • AspectJ 能干很多 Spring AOP 干不了的事情司致,它是 AOP 編程的完全解決方案。Spring AOP 致力于解決的是企業(yè)級開發(fā)中最普遍的 AOP 需求(方法織入)聋迎,而不是力求成為一個像 AspectJ 一樣的 AOP 編程完全解決方案脂矫。

  • 因為 AspectJ 在實際代碼運行前完成了織入,所以大家會說它生成的類是沒有額外運行時開銷的霉晕。

AOP 術語解釋

在這里庭再,不準備解釋那么多 AOP 編程中的術語了,我們碰到一個說一個吧牺堰。

Advice拄轻、Advisor、Pointcut伟葫、Aspect恨搓、Joinpoint 等等。

Spring AOP

首先要說明的是筏养,這里介紹的 Spring AOP 是純的 Spring 代碼奶卓,和 AspectJ 沒什么關系,但是 Spring 延用了 AspectJ 中的概念撼玄,包括使用了 AspectJ 提供的 jar 包中的注解夺姑,但是不依賴于其實現(xiàn)功能。

后面介紹的如 @Aspect掌猛、@Pointcut盏浙、@Before、@After 等注解都是來自于 AspectJ荔茬,但是功能的實現(xiàn)是純 Spring AOP 自己實現(xiàn)的废膘。

下面我們來介紹 Spring AOP 的使用方法,先從最簡單的配置方式開始說起慕蔚,這樣讀者想看源碼也會比較容易丐黄。

目前 Spring AOP 一共有三種配置方式,Spring 做到了很好地向下兼容孔飒,所以大家可以放心使用灌闺。

Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于幾個接口的艰争,想看源碼的同學可以從這里起步。
Spring 2.0 @AspectJ 配置:使用注解的方式來配置桂对,這種方式感覺是最方便的甩卓,還有,這里雖然叫做 @AspectJ蕉斜,但是這個和 AspectJ 其實沒啥關系逾柿。
Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式來配置,使用 命名空間 <aop />

Spring 1.2 中的配置

這節(jié)我們將介紹 Spring 1.2 中的配置宅此,這是最古老的配置机错,但是由于 Spring 提供了很好的向后兼容,以及很多人根本不知道什么配置是什么版本的父腕,以及是否有更新更好的配置方法替代弱匪,所以還是會有很多代碼是采用這種古老的配置方式的,這里說的古老并沒有貶義的意思侣诵。

下面用一個簡單的例子來演示怎么使用 Spring 1.2 的配置方式痢法。

首先狱窘,我們先定義兩個接口 UserServiceOrderService杜顺,以及它們的實現(xiàn)類 UserServiceImplOrderServiceImpl

UserService.png

OrderService.png

接下來,我們定義兩個 advice蘸炸,分別用于攔截方法執(zhí)行前和方法返回后:

advice 是我們接觸的第一個概念躬络,記住它是干什么用的。

Advice.png

上面的兩個 Advice 分別用于方法調用前輸出參數和方法調用后輸出結果搭儒。

現(xiàn)在可以開始配置了穷当,我們配置一個名為 spring_1_2.xml 的文件:


ProxyFactoryBean.png

接下來,我們跑起來看看:


main.png

查看輸出結果:

準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: queryUser, 參數列表:[]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}

從結果可以看到淹禾,對 UserService 中的兩個方法都做了前馁菜、后攔截。這個例子理解起來應該非常簡單铃岔,就是一個代理實現(xiàn)汪疮。

代理模式需要一個接口、一個具體實現(xiàn)類毁习,然后就是定義一個代理類智嚷,用來包裝實現(xiàn)類,添加自定義邏輯纺且,在使用的時候盏道,需要用代理類來生成實例。

此中方法有個致命的問題载碌,如果我們需要攔截 OrderService 中的方法猜嘱,那么我們還需要定義一個 OrderService 的代理衅枫。如果還要攔截 PostService,得定義一個 PostService 的代理......

而且泉坐,我們看到为鳄,我們的攔截器的粒度只控制到了類級別,類中所有的方法都進行了攔截腕让。接下來孤钦,我們看看怎么樣只攔截特定的方法

在上面的配置中纯丸,配置攔截器的時候偏形,interceptorNames 除了指定為 Advice,是還可以指定為 Interceptor 和 Advisor 的觉鼻。

這里我們來理解 Advisor 的概念俊扭,它也比較簡單,它內部需要指定一個 Advice坠陈,Advisor 決定該攔截哪些方法萨惑,攔截后需要完成的工作還是內部的 Advice 來做。

它有好幾個實現(xiàn)類仇矾,這里我們使用實現(xiàn)類 NameMatchMethodPointcutAdvisor 來演示庸蔼,從名字上就可以看出來,它需要我們給它提供方法名字贮匕,這樣符合該配置的方法才會做攔截姐仅。

NameMatchMethodPointcutAdvisor.png

我們可以看到,userServiceProxy 這個 bean 配置了一個 advisor刻盐,advisor 內部有一個 advice掏膏。advisor 負責匹配方法,內部的 advice 負責實現(xiàn)方法包裝敦锌。

注意馒疹,這里的 mappedNames 配置是可以指定多個的,用逗號分隔乙墙,可以是不同類中的方法颖变。相比直接指定 advice,advisor 實現(xiàn)了更細粒度的控制伶丐,因為在這里配置 advice 的話悼做,所有方法都會被攔截。


image.png

輸出結果如下哗魂,只有 createUser 方法被攔截:

準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
到這里肛走,我們已經了解了 Advice 和 Advisor 了,前面也說了還可以配置 Interceptor录别。

對于 Java 開發(fā)者來說朽色,對 Interceptor 這個概念肯定都很熟悉了邻吞,這里就不做演示了,貼一下實現(xiàn)代碼:

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        // 執(zhí)行 真實實現(xiàn)類 的方法
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

上面葫男,我們介紹完了 Advice抱冷、AdvisorInterceptor 三個概念梢褐,相信大家應該很容易就看懂它們了旺遮。

它們有個共同的問題,那就是我們得為每個 bean 都配置一個代理盈咳,之后獲取 bean 的時候需要獲取這個代理類的 bean 實例(如 (UserService) context.getBean("userServiceProxy"))耿眉,這顯然非常不方便,不利于我們之后要使用的自動根據類型注入鱼响。下面介紹 autoproxy 的解決方案鸣剪。

autoproxy:從名字我們也可以看出來,它是實現(xiàn)自動代理丈积,也就是說當 Spring 發(fā)現(xiàn)一個 bean 需要被切面織入的時候筐骇,Spring 會自動生成這個 bean 的一個代理來攔截方法的執(zhí)行,確保定義的切面能被執(zhí)行江滨。

這里強調自動铛纬,也就是說 Spring 會自動做這件事,而不用像前面介紹的牙寞,我們需要顯式地指定代理類的 bean饺鹃。

我們去掉原來的 ProxyFactoryBean 的配置莫秆,改為使用 BeanNameAutoProxyCreator 來配置:

BeanNameAutoProxyCreator.png

配置很簡單间雀,beanNames 中可以使用正則來匹配 bean 的名字。這樣配置出來以后镊屎,userServiceBeforeAdvice 和 userServiceAfterAdvice 這兩個攔截器就不僅僅可以作用于 UserServiceImpl 了惹挟,也可以作用于 OrderServiceImpl、PostServiceImpl缝驳、ArticleServiceImpl......等等连锯,也就是說不再是配置某個 bean 的代理了。

注意用狱,這里的 InterceptorNames 和前面一樣运怖,也是可以配置成 Advisor 和 Interceptor 的。

然后我們修改下使用的地方:


image.png

發(fā)現(xiàn)沒有夏伊,我們在使用的時候摇展,完全不需要關心代理了,直接使用原來的類型就可以了溺忧,這是非常方便的咏连。

輸出結果就是 OrderService 和 UserService 中的每個方法都得到了攔截:

準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: queryUser, 參數列表:[]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: createOrder, 參數列表:[Leo, 隨便買點什么]
方法返回:Order{username='Leo', product='隨便買點什么'}
準備執(zhí)行方法: queryOrder, 參數列表:[Leo]
方法返回:Order{username='Leo', product='隨便買點什么'}

到這里盯孙,是不是發(fā)現(xiàn) BeanNameAutoProxyCreator 非常好用,它需要指定被攔截類名的模式(如 *ServiceImpl)祟滴,它可以配置多次振惰,這樣就可以用來匹配不同模式的類了。

另外垄懂,在 BeanNameAutoProxyCreator 同一個包中骑晶,還有一個非常有用的類 DefaultAdvisorAutoProxyCreator,比上面的 BeanNameAutoProxyCreator 還要方便草慧。

之前我們說過透罢,advisor 內部包裝了 advice,advisor 負責決定攔截哪些方法冠蒋,內部 advice 定義攔截后的邏輯羽圃。所以,仔細想想其實就是只要讓我們的 advisor 全局生效就能實現(xiàn)我們需要的自定義攔截功能抖剿、攔截后的邏輯處理朽寞。

BeanNameAutoProxyCreator 是自己匹配方法,然后交由內部配置 advice 來攔截處理斩郎;
而 DefaultAdvisorAutoProxyCreator 是讓 ioc 容器中的所有 advisor 來匹配方法脑融,advisor 內部都是有 advice 的,讓它們內部的 advice 來執(zhí)行攔截處理缩宜。

1肘迎、我們需要再回頭看下 Advisor 的配置,上面我們用了 NameMatchMethodPointcutAdvisor 這個類:

<bean id="logCreateAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
    <property name="advice" ref="logArgsAdvice" />
    <property name="mappedNames" value="createUser,createOrder" />
</bean>

其實 Advisor 還有一個更加靈活的實現(xiàn)類 RegexpMethodPointcutAdvisor锻煌,它能實現(xiàn)正則匹配妓布,如:

<bean id="logArgsAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice" ref="logArgsAdvice" />
    <property name="pattern" value="com.javadoop.*.service.*.create.*" />
</bean>

也就是說,我們能通過配置 Advisor宋梧,精確定位到需要被攔截的方法匣沼,然后使用內部的 Advice 執(zhí)行邏輯處理。

2捂龄、之后释涛,我們需要配置 DefaultAdvisorAutoProxyCreator,它的配置非常簡單倦沧,直接使用下面這段配置就可以了唇撬,它就會使得所有的 Advisor 自動生效,無須其他配置展融。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
DefaultAdvisorAutoProxyCreator.png

然后我們運行一下:


main.png

輸出:

準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: createOrder, 參數列表:[Leo, 隨便買點什么]
方法返回:Order{username='Leo', product='隨便買點什么'}

從結果可以看出窖认,create 方法使用了 logArgsAdvisor 進行傳參輸出,query 方法使用了 logResultAdvisor 進行了返回結果輸出。

到這里耀态,Spring 1.2 的配置就要介紹完了轮傍。本文不會介紹得面面俱到,主要是關注最核心的配置首装,如果讀者感興趣创夜,要學會自己去摸索,比如這里的 Advisor 就不只有我這里介紹的 NameMatchMethodPointcutAdvisor 和 RegexpMethodPointcutAdvisor仙逻,AutoProxyCreator 也不僅僅是 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator驰吓。

讀到這里,我想對于很多人來說系奉,就知道怎么去閱讀 Spring AOP 源碼了檬贰。

Spring 2.0 @AspectJ 配置

Spring 2.0 以后,引入了 @AspectJ 和 Schema-based 的兩種配置方式缺亮,我們先來介紹 @AspectJ 的配置方式翁涤,之后我們再來看使用 xml 的配置方式。

注意了萌踱,@AspectJ 和 AspectJ 沒多大關系葵礼,并不是說基于 AspectJ 實現(xiàn)的,而僅僅是使用了 AspectJ 中的概念并鸵,包括使用的注解也是直接來自于 AspectJ 的包鸳粉。

首先,我們需要依賴 aspectjweaver.jar 這個包园担,這個包來自于 AspectJ:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.11</version>
</dependency>

如果是使用 Spring Boot 的話届谈,添加以下依賴即可:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因為我們需要使用 AspectJ 的處理功能弯汰,而是因為 Spring 使用了 AspectJ 提供的一些注解艰山,實際上還是純的 Spring AOP 代碼

說了這么多蝙泼,明確一點程剥,@AspectJ 采用注解的方式來配置使用 Spring AOP劝枣。

首先汤踏,我們需要開啟 @AspectJ 的注解配置方式,有兩種方式:

1舔腾、在 xml 中配置:

<aop:aspectj-autoproxy/>

2溪胶、使用 @EnableAspectJAutoProxy

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

一旦開啟了上面的配置,那么所有使用 @Aspect 注解的 bean 都會被 Spring 當做用來實現(xiàn) AOP 的配置類稳诚,我們稱之為一個 Aspect哗脖。

注意了,@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式才避,還是在 xml 中配置 bean橱夭,首先它需要是一個 bean。

比如下面這個 bean桑逝,它的類名上使用了 @Aspect棘劣,它就會被當做 Spring AOP 的配置。

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of aspect here as normal -->
</bean>
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

接下來楞遏,我們需要關心的是 @Aspect 注解的 bean 中茬暇,我們需要配置哪些內容。

首先寡喝,我們需要配置 Pointcut糙俗,Pointcut 在大部分地方被翻譯成切點,用于定義哪些方法需要被增強或者說需要被攔截预鬓,有點類似于之前介紹的 Advisor 的方法匹配巧骚。

Spring AOP 只支持 bean 中的方法(不像 AspectJ 那么強大),所以我們可以認為 Pointcut 就是用來匹配 Spring 容器中的所有 bean 的方法的格二。

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

我們看到网缝,@Pointcut 中使用了 execution 來正則匹配方法簽名,這也是最常用的蟋定,除了 execution粉臊,我們再看看其他的幾個比較常用的匹配方式:

  • within:指定所在類或所在包下面的方法(Spring AOP 獨有)

如 @Pointcut("within(com.javadoop.springaoplearning.service..*)")

  • @annotation:方法上具有特定的注解,如 @Subscribe 用于訂閱特定的事件驶兜。

如 @Pointcut("execution( .*(..)) && @annotation(com.javadoop.annotation.Subscribe)")

  • bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 獨有)

如 @Pointcut("bean(*Service)")

Tips:上面匹配中扼仲,通常 "." 代表一個包名,".." 代表包及其子包抄淑,方法參數任意匹配使用兩個點 ".."屠凶。

對于 web 開發(fā)者,Spring 有個很好的建議肆资,就是定義一個 SystemArchitecture:

@Aspect
public class SystemArchitecture {

    // web 層
    @Pointcut("within(com.javadoop.web..*)")
    public void inWebLayer() {}

    // service 層
    @Pointcut("within(com.javadoop.service..*)")
    public void inServiceLayer() {}

    // dao 層
    @Pointcut("within(com.javadoop.dao..*)")
    public void inDataAccessLayer() {}

    // service 實現(xiàn)矗愧,注意這里指的是方法實現(xiàn),其實通常也可以使用 bean(*ServiceImpl)
    @Pointcut("execution(* com.javadoop..service.*.*(..))")
    public void businessService() {}

    // dao 實現(xiàn)
    @Pointcut("execution(* com.javadoop.dao.*.*(..))")
    public void dataAccessOperation() {}
}

上面這個 SystemArchitecture 很好理解郑原,該 Aspect 定義了一堆的 Pointcut唉韭,隨后在任何需要 Pointcut 的地方都可以直接引用(如 xml 中的 pointcut-ref="")。

配置 pointcut 就是配置我們需要攔截哪些方法犯犁,接下來属愤,我們要配置需要對這些被攔截的方法做什么,也就是前面介紹的 Advice酸役。

接下來住诸,我們要配置 Advice驾胆。

下面這塊代碼示例了各種常用的情況:

注意,實際寫代碼的時候贱呐,不要把所有的切面都揉在一個 class 中丧诺。

@Aspect
public class AdviceExample {

    // 這里會用到我們前面說的 SystemArchitecture
    // 下面方法就是寫攔截 "dao層實現(xiàn)"
    @Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ... 實現(xiàn)代碼
    }

    // 當然,我們也可以直接"內聯(lián)"Pointcut奄薇,直接在這里定義 Pointcut
    // 把 Advice 和 Pointcut 合在一起了锅必,但是這兩個概念我們還是要區(qū)分清楚的
    @Before("execution(* com.javadoop.dao.*.*(..))")
    public void doAccessCheck() {
        // ... 實現(xiàn)代碼
    }

    @AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

    @AfterReturning(
        pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // 這樣,進來這個方法的處理時候惕艳,retVal 就是相應方法的返回值搞隐,是不是非常方便
        //  ... 實現(xiàn)代碼
    }

    // 異常返回
    @AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ... 實現(xiàn)代碼
    }

    @AfterThrowing(
        pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ... 實現(xiàn)代碼
    }

    // 注意理解它和 @AfterReturning 之間的區(qū)別,這里會攔截正常返回和異常的情況
    @After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // 通常就像 finally 塊一樣使用远搪,用來釋放資源劣纲。
        // 無論正常返回還是異常退出,都會被攔截到
    }

    // 感覺這個很有用吧谁鳍,既能做 @Before 的事情癞季,也可以做 @AfterReturning 的事情
    @Around("com.javadoop.aop.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

細心的讀者可能發(fā)現(xiàn)了有些 Advice 缺少方法傳參,如在 @Before 場景中參數往往是非常有用的倘潜,比如我們要用日志記錄下來被攔截方法的入參情況绷柒。

Spring 提供了非常簡單的獲取入參的方法,使用 org.aspectj.lang.JoinPoint 作為 Advice 的第一個參數即可涮因,如:

@Before("com.javadoop.springaoplearning.aop_spring_2_aspectj.SystemArchitecture.businessService()")
public void logArgs(JoinPoint joinPoint) {
    System.out.println("方法執(zhí)行前废睦,打印入參:" + Arrays.toString(joinPoint.getArgs()));
}

注意:第一,必須放置在第一個參數上养泡;第二嗜湃,如果是 @Around,我們通常會使用其子類 ProceedingJoinPoint澜掩,因為它有 procceed()/procceed(args[]) 方法购披。

到這里,我們介紹完了 @AspectJ 配置方式中的 Pointcut 和 Advice 的配置肩榕。對于開發(fā)者來說刚陡,其實最重要的就是這兩個了,定義 Pointcut 和使用合適的 Advice 在各個 Pointcut 上株汉。

下面筐乳,我們用這一節(jié)介紹的 @AspectJ 來實現(xiàn)上一節(jié)實現(xiàn)的記錄方法傳參和記錄方法返回值。

13

xml 的配置非常簡單:

14

這里是示例郎逃,所以 bean 的配置還是使用了 xml 的配置方式哥童。

測試一下:

15

輸出結果:

方法執(zhí)行前,打印入參:[Tom, Cruise, 55]
User{firstName='Tom', lastName='Cruise', age=55, address='null'}
方法執(zhí)行前褒翰,打印入參:[]
User{firstName='Tom', lastName='Cruise', age=55, address='null'}

JoinPoint 除了 getArgs() 外還有一些有用的方法,大家可以進去稍微看一眼。

最后提一點优训,@Aspect 中的配置不會作用于使用 @Aspect 注解的 bean朵你。

Spring 2.0 schema-based 配置

本節(jié)將介紹的是 Spring 2.0 以后提供的基于 <aop /> 命名空間的 XML 配置。這里說的 schema-based 就是指基于 aop 這個 schema揣非。

介紹 IOC 的時候也介紹過 Spring 是怎么解析各個命名空間的(各種 *NamespaceHandler)抡医,解析 <aop /> 的源碼在 org.springframework.aop.config.AopNamespaceHandler 中。

有了前面的 @AspectJ 的配置方式的知識早敬,理解 xml 方式的配置非常簡單忌傻,所以我們就可以廢話少一點了。

這里先介紹配置 Aspect搞监,便于后續(xù)理解:

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

所有的配置都在 <aop:config> 下面水孩。

<aop:aspect > 中需要指定一個 bean,和前面介紹的 LogArgsAspect 和 LogResultAspect 一樣琐驴,我們知道該 bean 中我們需要寫處理代碼俘种。

然后,我們寫好 Aspect 代碼后绝淡,將其“織入”到合適的 Pointcut 中宙刘,這就是面向切面。

然后牢酵,我們需要配置 Pointcut悬包,非常簡單,如下:

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.javadoop.springaoplearning.service.*.*(..))"/>

    <!--也可以像下面這樣-->
    <aop:pointcut id="businessService2"
        expression="com.javadoop.SystemArchitecture.businessService()"/>

</aop:config>

將 <aop:pointcut> 作為 <aop:config> 的直接子元素馍乙,將作為全局 Pointcut玉罐。

我們也可以在 <aop:aspect />內部配置 Pointcut,這樣該 Pointcut 僅用于該 Aspect:

<aop:config>
    <aop:aspect ref="logArgsAspect">
        <aop:pointcut id="internalPointcut"
                expression="com.javadoop.SystemArchitecture.businessService()" />
    </aop:aspect>
</aop:config>

接下來潘拨,我們應該配置 Advice 了吊输,為了避免廢話過多,我們直接上實例吧铁追,非常好理解季蚂,將上一節(jié)用 @AspectJ 方式配置的搬過來:


image.png

上面的例子中,我們配置了兩個 LogArgsAspect 和一個 LogResultAspect琅束。

其實基于 XML 的配置也是非常靈活的扭屁,這里沒辦法給大家演示各種搭配,大家抓住基本的 Pointcut涩禀、Advice 和 Aspect 這幾個概念料滥,就很容易配置了。

小結

到這里艾船,本文介紹了 Spring AOP 的三種配置方式葵腹,我們要知道的是高每,到目前為止,我們使用的都是 Spring AOP践宴,和 AspectJ 沒什么關系鲸匿。

下一篇文章,將會介紹 AspectJ 的使用方式阻肩,以及怎樣在 Spring 應用中使用 AspectJ带欢。之后差不多就可以出 Spring AOP 源碼分析了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末烤惊,一起剝皮案震驚了整個濱河市乔煞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柒室,老刑警劉巖渡贾,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伦泥,居然都是意外死亡剥啤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門不脯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來府怯,“玉大人,你說我怎么就攤上這事防楷∥” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵复局,是天一觀的道長冲簿。 經常有香客問我,道長亿昏,這世上最難降的妖魔是什么峦剔? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮角钩,結果婚禮上吝沫,老公的妹妹穿的比我還像新娘。我一直安慰自己递礼,他們只是感情好惨险,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脊髓,像睡著了一般辫愉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上将硝,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天恭朗,我揣著相機與錄音屏镊,去河邊找鬼。 笑死冀墨,一個胖子當著我的面吹牛闸衫,可吹牛的內容都是我干的涛贯。 我是一名探鬼主播诽嘉,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼煌抒,長吁一口氣:“原來是場噩夢啊……” “哼询刹!你這毒婦竟也來了?” 一聲冷哼從身側響起妥畏,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稀余,失蹤者是張志新(化名)和其女友劉穎悦冀,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體睛琳,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡盒蟆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了师骗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片历等。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辟癌,靈堂內的尸體忽然破棺而出寒屯,到底是詐尸還是另有隱情,我是刑警寧澤黍少,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布寡夹,位于F島的核電站,受9級特大地震影響厂置,放射性物質發(fā)生泄漏菩掏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一昵济、第九天 我趴在偏房一處隱蔽的房頂上張望智绸。 院中可真熱鬧,春花似錦砸紊、人聲如沸传于。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沼溜。三九已至,卻和暖如春游添,著一層夾襖步出監(jiān)牢的瞬間系草,已是汗流浹背通熄。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留找都,地道東北人唇辨。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像能耻,于是被迫代替她去往敵國和親赏枚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容

  • 本文是我自己在秋招復習時的讀書筆記晓猛,整理的知識點饿幅,也是為了防止忘記,尊重勞動成果戒职,轉載注明出處哦栗恩!如果你也喜歡,那...
    波波波先森閱讀 12,297評論 6 86
  • 本博中關于spring的文章:Spring IOC和AOP原理洪燥,Spring事務原理探究磕秤,Spring配置文件屬性...
    Maggie編程去閱讀 4,106評論 0 34
  • 概述 Spring是什么? Spring是一個開源框架捧韵,為了解決企業(yè)應用開發(fā)的復雜性而創(chuàng)建的市咆,但是現(xiàn)在已經不止于企...
    瑯筑閱讀 1,172評論 2 8
  • 一、AOP的基礎 1.1纫版、AOP是什么床绪??其弊? 考慮這樣一個問題:需要對系統(tǒng)中的某些業(yè)務做日志記錄癞己,比如支付系統(tǒng)中的...
    聶叼叼閱讀 2,117評論 2 17
  • 文|啊惜 01 太陽 第一個特別要記在小本本上的是夏天福州的天氣痹雅! 本周手機推送的天氣預報都是“高溫橙色預警”(空...
    赴里閱讀 622評論 0 2