用抽兩根煙的時(shí)間掌握Spring AOP——Spring 中面向切面編程

一槐秧、AOP——另一種編程思想

1.1 什么是 AOP

AOP (Aspect Orient Programming),直譯過來就是 面向切面編程光酣。AOP 是一種編程思想蜘矢,是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充。面向?qū)ο缶幊虒⒊绦虺橄蟪筛鱾€(gè)層次的對(duì)象敢靡,而面向切面編程是將程序抽象成各個(gè)切面。
從《Spring實(shí)戰(zhàn)(第4版)》圖書中扒了一張圖:


從該圖可以很形象地看出苦银,所謂切面啸胧,相當(dāng)于應(yīng)用對(duì)象間的橫切點(diǎn)赶站,我們可以將其單獨(dú)抽象為單獨(dú)的模塊。

1.2 為什么需要 AOP

想象下面的場(chǎng)景纺念,開發(fā)中在多個(gè)模塊間有某段重復(fù)的代碼贝椿,我們通常是怎么處理的?顯然陷谱,沒有人會(huì)靠“復(fù)制粘貼”吧烙博。在傳統(tǒng)的面向過程編程中,我們也會(huì)將這段代碼烟逊,抽象成一個(gè)方法渣窜,然后在需要的地方分別調(diào)用這個(gè)方法,這樣當(dāng)這段代碼需要修改時(shí)宪躯,我們只需要改變這個(gè)方法就可以了乔宿。然而需求總是變化的,有一天眷唉,新增了一個(gè)需求予颤,需要再多出做修改,我們需要再抽象出一個(gè)方法冬阳,然后再在需要的地方分別調(diào)用這個(gè)方法蛤虐,又或者我們不需要這個(gè)方法了,我們還是得刪除掉每一處調(diào)用該方法的地方肝陪。實(shí)際上涉及到多個(gè)地方具有相同的修改的問題我們都可以通過 AOP 來解決驳庭。

1.3 AOP 實(shí)現(xiàn)分類

AOP 要達(dá)到的效果是,保證開發(fā)者不修改源代碼的前提下氯窍,去為系統(tǒng)中的業(yè)務(wù)組件添加某種通用功能饲常。AOP 的本質(zhì)是由 AOP 框架修改業(yè)務(wù)組件的多個(gè)方法的源代碼,看到這其實(shí)應(yīng)該明白了狼讨,AOP 其實(shí)就是前面一篇文章講的代理模式的典型應(yīng)用贝淤。
按照 AOP 框架修改源代碼的時(shí)機(jī),可以將其分為兩類:

  • 靜態(tài) AOP 實(shí)現(xiàn)政供, AOP 框架在編譯階段對(duì)程序源代碼進(jìn)行修改播聪,生成了靜態(tài)的 AOP 代理類(生成的 *.class 文件已經(jīng)被改掉了,需要使用特定的編譯器)布隔,比如 AspectJ离陶。
  • 動(dòng)態(tài) AOP 實(shí)現(xiàn), AOP 框架在運(yùn)行階段對(duì)動(dòng)態(tài)生成代理對(duì)象(在內(nèi)存中以 JDK 動(dòng)態(tài)代理衅檀,或 CGlib 動(dòng)態(tài)地生成 AOP 代理類)招刨,如 SpringAOP。

下面給出常用 AOP 實(shí)現(xiàn)比較


整理了一份

spring學(xué)習(xí)筆記和相關(guān)面試題

需要的朋友可自行點(diǎn)擊領(lǐng)取哀军。

二沉眶、AOP 術(shù)語(yǔ)

AOP 領(lǐng)域中的特性術(shù)語(yǔ):

  • 通知(Advice): AOP 框架中的增強(qiáng)處理打却。通知描述了切面何時(shí)執(zhí)行以及如何執(zhí)行增強(qiáng)處理。
  • 連接點(diǎn)(join point): 連接點(diǎn)表示應(yīng)用執(zhí)行過程中能夠插入切面的一個(gè)點(diǎn)沦寂,這個(gè)點(diǎn)可以是方法的調(diào)用学密、異常的拋出。在 Spring AOP 中传藏,連接點(diǎn)總是方法的調(diào)用腻暮。
  • 切點(diǎn)(PointCut): 可以插入增強(qiáng)處理的連接點(diǎn)。
  • 切面(Aspect): 切面是通知和切點(diǎn)的結(jié)合毯侦。
  • 引入(Introduction):引入允許我們向現(xiàn)有的類添加新的方法或者屬性哭靖。
  • 織入(Weaving): 將增強(qiáng)處理添加到目標(biāo)對(duì)象中,并創(chuàng)建一個(gè)被增強(qiáng)的對(duì)象侈离,這個(gè)過程就是織入试幽。

概念看起來總是有點(diǎn)懵,并且上述術(shù)語(yǔ)卦碾,不同的參考書籍上翻譯還不一樣铺坞,所以需要慢慢在應(yīng)用中理解。

三洲胖、初步認(rèn)識(shí) Spring AOP

3.1 Spring AOP 的特點(diǎn)

AOP 框架有很多種济榨,1.3節(jié)中介紹了 AOP 框架的實(shí)現(xiàn)方式有可能不同, Spring 中的 AOP 是通過動(dòng)態(tài)代理實(shí)現(xiàn)的绿映。不同的 AOP 框架支持的連接點(diǎn)也有所區(qū)別擒滑,例如,AspectJ 和 JBoss,除了支持方法切點(diǎn)叉弦,它們還支持字段和構(gòu)造器的連接點(diǎn)丐一。而 Spring AOP 不能攔截對(duì)對(duì)象字段的修改,也不支持構(gòu)造器連接點(diǎn),我們無法在 Bean 創(chuàng)建時(shí)應(yīng)用通知淹冰。

3.2 Spring AOP 的簡(jiǎn)單例子

下面先上代碼库车,對(duì)著代碼說比較好說,看下面這個(gè)例子:
這個(gè)例子是基于gradle創(chuàng)建的樱拴,首先 build.gradle 文件添加依賴:

dependencies {
    compile 'org.springframework:spring-context:5.0.6.RELEASE'
}

首先創(chuàng)建一個(gè)接口 IBuy.java

package com.sharpcj.aopdemo.test1;

public interface IBuy {
    String buy();
}

Boy 和 Gril 兩個(gè)類分別實(shí)現(xiàn)了這個(gè)接口:
Boy.java

package com.sharpcj.aopdemo.test1;

import org.springframework.stereotype.Component;

@Component
public class Boy implements IBuy {
    @Override
    public String buy() {
        System.out.println("男孩買了一個(gè)游戲機(jī)");
        return "游戲機(jī)";
    }
}

Girl.java

package com.sharpcj.aopdemo.test1;

import org.springframework.stereotype.Component;

@Component
public class Girl implements IBuy {
    @Override
    public String buy() {
        System.out.println("女孩買了一件漂亮的衣服");
        return "衣服";
    }
}

配置文件, AppConfig.java

package com.sharpcj.aopdemo;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
public class AppConfig {
}

測(cè)試類凝颇, AppTest.java

package com.sharpcj.aopdemo;

import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AppTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Boy boy = context.getBean("boy",Boy.class);
        Girl girl = (Girl) context.getBean("girl");
        boy.buy();
        girl.buy();
    }
}

運(yùn)行結(jié)果:


image

這里運(yùn)用SpringIOC里的自動(dòng)部署。現(xiàn)在需求改變了疹鳄,我們需要在男孩和女孩的 buy 方法之前,需要打印出“男孩女孩都買了自己喜歡的東西”芦岂。用 Spring AOP 來實(shí)現(xiàn)這個(gè)需求只需下面幾個(gè)步驟:
1瘪弓、 既然用到 Spring AOP, 首先在 build.gralde 文件中引入相關(guān)依賴:

dependencies {
    compile 'org.springframework:spring-context:5.0.6.RELEASE'
    compile 'org.springframework:spring-aspects:5.0.6.RELEASE'
}

2、 定義一個(gè)切面類禽最,BuyAspectJ.java

package com.sharpcj.aopdemo.test1;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class BuyAspectJ {
    @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void haha(){
        System.out.println("男孩女孩都買自己喜歡的東西");
    }
}

這個(gè)類腺怯,我們使用了注解 @Component 表明它將作為一個(gè)Spring Bean 被裝配袱饭,使用注解 @Aspect 表示它是一個(gè)切面。
類中只有一個(gè)方法 haha 我們使用 @Before 這個(gè)注解呛占,表示他將在方法執(zhí)行之前執(zhí)行虑乖。關(guān)于這個(gè)注解后文再作解釋。
參數(shù)("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))") 聲明了切點(diǎn)晾虑,表明在該切面的切點(diǎn)是com.sharpcj.aopdemo.test1.Ibuy這個(gè)接口中的buy方法疹味。至于為什么這么寫,下文再解釋帜篇。
3糙捺、 在配置文件中啟用AOP切面功能

package com.sharpcj.aopdemo;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}

我們?cè)谂渲梦募愒黾恿?code>@EnableAspectJAutoProxy注解,啟用了 AOP 功能笙隙,參數(shù)proxyTargetClass的值設(shè)為了 true 洪灯。默認(rèn)值是 false,兩者的區(qū)別下文再解釋竟痰。
OK签钩,下面只需測(cè)試代碼,運(yùn)行結(jié)果如下:

我們看到,結(jié)果與我們需求一致坏快,我們并沒有修改 Boy 和 Girl 類的 Buy 方法铅檩,也沒有修改測(cè)試類的代碼,幾乎是完全無侵入式地實(shí)現(xiàn)了需求假消。這就是 AOP 的“神奇”之處柠并。

四、通過注解配置 Spring AOP

4.1 通過注解聲明切點(diǎn)指示器

Spring AOP 所支持的 AspectJ 切點(diǎn)指示器


在spring中嘗試使用AspectJ其他指示器時(shí)富拗,將會(huì)拋出IllegalArgumentException異常臼予。

當(dāng)我們查看上面展示的這些spring支持的指示器時(shí),注意只有execution指示器是唯一的執(zhí)行匹配啃沪,而其他的指示器都是用于限制匹配的粘拾。這說明execution指示器是我們?cè)诰帉懬悬c(diǎn)定義時(shí)最主要使用的指示器,在此基礎(chǔ)上创千,我們使用其他指示器來限制所匹配的切點(diǎn)缰雇。

下圖的切點(diǎn)表達(dá)式表示當(dāng)Instrument的play方法執(zhí)行時(shí)會(huì)觸發(fā)通知。


我們使用execution指示器選擇Instrument的play方法追驴,方法表達(dá)式以 * 號(hào)開始械哟,標(biāo)識(shí)我們不關(guān)心方法的返回值類型。然后我們指定了全限定類名和方法名殿雪。對(duì)于方法參數(shù)列表暇咆,我們使用 .. 標(biāo)識(shí)切點(diǎn)選擇任意的play方法,無論該方法的入?yún)⑹鞘裁础?br> 多個(gè)匹配之間我們可以使用鏈接符 &&||爸业、其骄!來表示 “且”、“或”扯旷、“非”的關(guān)系拯爽。但是在使用 XML 文件配置時(shí),這些符號(hào)有特殊的含義钧忽,所以我們使用 “and”毯炮、“or”、“not”來表示惰瓜。

舉例:
限定該切點(diǎn)僅匹配的包是 com.sharpcj.aopdemo.test1,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*)
在切點(diǎn)中選擇 bean,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && bean(girl)
修改 BuyAspectJ.java

package com.sharpcj.aopdemo.test1;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class BuyAspectJ {
    @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")
    public void hehe(){
        System.out.println("男孩女孩都買自己喜歡的東西");
    }
}

此時(shí)否副,切面只會(huì)對(duì) Girl.java 這個(gè)類生效,執(zhí)行結(jié)果:

細(xì)心的你崎坊,可能發(fā)現(xiàn)了备禀,切面中的方法名,已經(jīng)被我悄悄地從haha改成了hehe奈揍,絲毫沒有影響結(jié)果曲尸,說明方法名沒有影響。和 Spring IOC 中用 java 配置文件裝配 Bean 時(shí)男翰,用@Bean 注解修飾的方法名一樣另患,沒有影響。

4.2 通過注解聲明 5 種通知類型

Spring AOP 中有 5 中通知類型蛾绎,分別如下:


下面修改切面類:

package com.sharpcj.aopdemo.test1;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class BuyAspectJ {
    @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void hehe() {
        System.out.println("before ...");
    }

    @After("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void haha() {
        System.out.println("After ...");
    }

    @AfterReturning("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void xixi() {
        System.out.println("AfterReturning ...");
    }

    @Around("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void xxx(ProceedingJoinPoint pj) {
        try {
            System.out.println("Around aaa ...");
            pj.proceed();
            System.out.println("Around bbb ...");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

}

為了方便看效果,我們測(cè)試類中昆箕,只要 Boy 類:

package com.sharpcj.aopdemo;

import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AppTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Boy boy = context.getBean("boy",Boy.class);
        Girl girl = (Girl) context.getBean("girl");
        boy.buy();
        // girl.buy();
    }
}

執(zhí)行結(jié)果如下:


結(jié)果顯而易見。指的注意的是 @Around 修飾的環(huán)繞通知類型租冠,是將整個(gè)目標(biāo)方法封裝起來了鹏倘,在使用時(shí),我們傳入了 ProceedingJoinPoint 類型的參數(shù)顽爹,這個(gè)對(duì)象是必須要有的纤泵,并且需要調(diào)用 ProceedingJoinPointproceed() 方法。 如果沒有調(diào)用 該方法镜粤,執(zhí)行結(jié)果為 :

Around aaa ...
Around bbb ...
After ...
AfterReturning ...

可見捏题,如果不調(diào)用該對(duì)象的 proceed() 方法,表示原目標(biāo)方法被阻塞調(diào)用肉渴,當(dāng)然也有可能你的實(shí)際需求就是這樣公荧。

4.3 通過注解聲明切點(diǎn)表達(dá)式

如你看到的,上面我們寫的多個(gè)通知使用了相同的切點(diǎn)表達(dá)式同规,對(duì)于像這樣頻繁出現(xiàn)的相同的表達(dá)式稚矿,我們可以使用 @Pointcut注解聲明切點(diǎn)表達(dá)式庸诱,然后使用表達(dá)式,修改代碼如下:
BuyAspectJ.java

package com.sharpcj.aopdemo.test1;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class BuyAspectJ {

    @Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void point(){}

    @Before("point()")
    public void hehe() {
        System.out.println("before ...");
    }

    @After("point()")
    public void haha() {
        System.out.println("After ...");
    }

    @AfterReturning("point()")
    public void xixi() {
        System.out.println("AfterReturning ...");
    }

    @Around("point()")
    public void xxx(ProceedingJoinPoint pj) {
        try {
            System.out.println("Around aaa ...");
            pj.proceed();
            System.out.println("Around bbb ...");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

程序運(yùn)行結(jié)果沒有變化晤揣。
這里,我們使用

@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}

聲明了一個(gè)切點(diǎn)表達(dá)式朱灿,該方法 point 的內(nèi)容并不重要昧识,方法名也不重要,實(shí)際上它只是作為一個(gè)標(biāo)識(shí)盗扒,供通知使用跪楞。

4.4 通過注解處理通知中的參數(shù)

上面的例子,我們要進(jìn)行增強(qiáng)處理的目標(biāo)方法沒有參數(shù)侣灶,下面我們來說說有參數(shù)的情況甸祭,并且在增強(qiáng)處理中使用該參數(shù)。
下面我們給接口增加一個(gè)參數(shù)褥影,表示購(gòu)買所花的金錢池户。通過AOP 增強(qiáng)處理,如果女孩買衣服超過了 68 元凡怎,就可以贈(zèng)送一雙襪子校焦。
更改代碼如下:
IBuy.java

package com.sharpcj.aopdemo.test1;

public interface IBuy {
    String buy(double price);
}

Girl.java

package com.sharpcj.aopdemo.test1;

import org.springframework.stereotype.Component;

@Component
public class Girl implements IBuy {
    @Override
    public String buy(double price) {
        System.out.println(String.format("女孩花了%s元買了一件漂亮的衣服", price));
        return "衣服";
    }
}

Boy.java

package com.sharpcj.aopdemo.test1;

import org.springframework.stereotype.Component;

@Component
public class Boy implements IBuy {
    @Override
    public String buy(double price) {
        System.out.println(String.format("男孩花了%s元買了一個(gè)游戲機(jī)", price));
        return "游戲機(jī)";
    }
}

再看 BuyAspectJ 類,我們將之前的通知都注釋掉统倒。用一個(gè)環(huán)繞通知來實(shí)現(xiàn)這個(gè)功能:

package com.sharpcj.aopdemo.test1;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class BuyAspectJ {

    /*
    @Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void point(){}

    @Before("point()")
    public void hehe() {
        System.out.println("before ...");
    }

    @After("point()")
    public void haha() {
        System.out.println("After ...");
    }

    @AfterReturning("point()")
    public void xixi() {
        System.out.println("AfterReturning ...");
    }

    @Around("point()")
    public void xxx(ProceedingJoinPoint pj) {
        try {
            System.out.println("Around aaa ...");
            pj.proceed();
            System.out.println("Around bbb ...");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
    */

    @Pointcut("execution(String com.sharpcj.aopdemo.test1.IBuy.buy(double)) && args(price) && bean(girl)")
    public void gif(double price) {
    }

    @Around("gif(price)")
    public String hehe(ProceedingJoinPoint pj, double price){
        try {
            pj.proceed();
            if (price > 68) {
                System.out.println("女孩買衣服超過了68元寨典,贈(zèng)送一雙襪子");
                return "衣服和襪子";
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return "衣服";
    }
}

前文提到,當(dāng)不關(guān)心方法返回值的時(shí)候房匆,我們?cè)诰帉懬悬c(diǎn)指示器的時(shí)候使用了 * 耸成, 當(dāng)不關(guān)心方法參數(shù)的時(shí)候,我們使用了 ..≡『瑁現(xiàn)在如果我們需要傳入?yún)?shù)井氢,并且有返回值的時(shí)候,則需要使用對(duì)應(yīng)的類型赚楚。在編寫通知的時(shí)候毙沾,我們也需要聲明對(duì)應(yīng)的返回值類型和參數(shù)類型。

測(cè)試類:AppTest.java

package com.sharpcj.aopdemo;

import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AppTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Boy boy = context.getBean("boy",Boy.class);
        Girl girl = (Girl) context.getBean("girl");
        String boyBought = boy.buy(35);
        String girlBought = girl.buy(99.8);

        System.out.println("男孩買到了:" + boyBought);
        System.out.println("女孩買到了:" + girlBought);
    }
}

測(cè)試結(jié)果:


可以看到宠页,我們成功通過 AOP 實(shí)現(xiàn)了需求左胞,并將結(jié)果打印了出來。

4.5 通過注解配置織入的方式

前面還有一個(gè)遺留問題举户,在配置文件中烤宙,我們用注解 @EnableAspectJAutoProxy() 啟用Spring AOP 的時(shí)候,我們給參數(shù) proxyTargetClass 賦值為 true,如果我們不寫參數(shù)俭嘁,默認(rèn)為 false躺枕。這個(gè)時(shí)候運(yùn)行程序,程序拋出異常

這是一個(gè)強(qiáng)制類型轉(zhuǎn)換異常。為什么會(huì)拋出這個(gè)異常呢拐云?或許已經(jīng)能夠想到罢猪,這跟Spring AOP 動(dòng)態(tài)代理的機(jī)制有關(guān),這個(gè) proxyTargetClass 參數(shù)決定了代理的機(jī)制叉瘩。當(dāng)這個(gè)參數(shù)為 false 時(shí)膳帕,
通過jdk的基于接口的方式進(jìn)行織入,這時(shí)候代理生成的是一個(gè)接口對(duì)象薇缅,將這個(gè)接口對(duì)象強(qiáng)制轉(zhuǎn)換為實(shí)現(xiàn)該接口的一個(gè)類危彩,自然就拋出了上述類型轉(zhuǎn)換異常。
反之泳桦,proxyTargetClasstrue汤徽,則會(huì)使用 cglib 的動(dòng)態(tài)代理方式。這種方式的缺點(diǎn)是拓展類的方法被final修飾時(shí)灸撰,無法進(jìn)行織入谒府。
測(cè)試一下,我們將 proxyTargetClass 參數(shù)設(shè)為 true梧奢,同時(shí)將 Girl.java 的 Buy 方法用 final 修飾:
AppConfig.java

package com.sharpcj.aopdemo;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}

Girl.java

package com.sharpcj.aopdemo.test1;

import org.springframework.stereotype.Component;

@Component
public class Girl implements IBuy {
    @Override
    public final String buy(double price) {
        System.out.println(String.format("女孩花了%s元買了一件漂亮的衣服", price));
        return "衣服";
    }
}

此時(shí)運(yùn)行結(jié)果:


可以看到狱掂,我們的切面并沒有織入生效。

五亲轨、通過 XML 配置文件聲明切面

前面的示例中趋惨,我們已經(jīng)展示了如何通過注解配置去聲明切面,下面我們看看如何在 XML 文件中聲明切面惦蚊。下面先列出 XML 中聲明 AOP 的常用元素:


我們依然可以使用 <aop:aspectj-autoproxy> 元素器虾,他能夠自動(dòng)代理AspectJ注解的通知類。

5.1 XML 配置文件中切點(diǎn)指示器

在XML配置文件中蹦锋,切點(diǎn)指示器表達(dá)式與通過注解配置的寫法基本一致兆沙,區(qū)別前面有提到,即XML文件中需要使用 “and”莉掂、“or”葛圃、“not”來表示 “且”、“或”憎妙、“非”的關(guān)系库正。

5.2 XML 文件配置 AOP 實(shí)例

下面我們不使用任何注解改造上面的例子:
BuyAspectJ.java

package com.sharpcj.aopdemo.test2;

import org.aspectj.lang.ProceedingJoinPoint;

public class BuyAspectJ {

    public void hehe() {
        System.out.println("before ...");
    }

    public void haha() {
        System.out.println("After ...");
    }

    public void xixi() {
        System.out.println("AfterReturning ...");
    }

    public void xxx(ProceedingJoinPoint pj) {
        try {
            System.out.println("Around aaa ...");
            pj.proceed();
            System.out.println("Around bbb ...");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

在 Resource 目錄下新建一個(gè)配置文件 aopdemo.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
    <bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
    <bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>

    <aop:config proxy-target-class="true">
        <aop:aspect id="qiemian" ref="buyAspectJ">
            <aop:before pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="hehe"/>
            <aop:after pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="haha"/>
            <aop:after-returning pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xixi"/>
            <aop:around pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xxx"/>
        </aop:aspect>
    </aop:config>
</beans>

這里分別定義了一個(gè)切面,里面包含四種類型的通知厘唾。
測(cè)試文件中褥符,使用

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aopdemo.xml");

來獲取 ApplicationContext,其它代碼不變抚垃。

5.3 XML 文件配置聲明切點(diǎn)

對(duì)于頻繁重復(fù)使用的切點(diǎn)表達(dá)式喷楣,我們也可以聲明成切點(diǎn)趟大。
配置文件如下:aopdemo.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
    <bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
    <bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>

    <aop:config proxy-target-class="true">
        <aop:pointcut id="apoint" expression="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))"/>
        <aop:aspect id="qiemian" ref="buyAspectJ">
            <aop:before pointcut-ref="apoint" method="hehe"/>
            <aop:after pointcut-ref="apoint" method="haha"/>
            <aop:after-returning pointcut-ref="apoint" method="xixi"/>
            <aop:around pointcut-ref="apoint" method="xxx"/>
        </aop:aspect>
    </aop:config>
</beans>

5.4 XML文件配置為通知傳遞參數(shù)

BuyAspectJ.java

package com.sharpcj.aopdemo.test2;

import org.aspectj.lang.ProceedingJoinPoint;

public class BuyAspectJ {
public String hehe(ProceedingJoinPoint pj, double price){
        try {
            pj.proceed();
            if (price > 68) {
                System.out.println("女孩買衣服超過了68元,贈(zèng)送一雙襪子");
                return "衣服和襪子";
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return "衣服";
    }
}

aopdemo.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
    <bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
    <bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>

    <aop:config proxy-target-class="true">
        <aop:pointcut id="apoint" expression="execution(String com.sharpcj.aopdemo.test2.IBuy.buy(double)) and args(price) and bean(girl)"/>
        <aop:aspect id="qiemian" ref="buyAspectJ">
            <aop:around pointcut-ref="apoint" method="hehe"/>
        </aop:aspect>
    </aop:config>
</beans>

5.5 Xml 文件配置織入的方式

同注解配置類似,
CGlib 代理方式:

<aop:config proxy-target-class="true"> </aop:config>

JDK 代理方式:

<aop:config proxy-target-class="false"> </aop:config>

六铣焊、總結(jié)

本文簡(jiǎn)單記錄了 AOP 的編程思想逊朽,然后介紹了 Spring 中 AOP 的相關(guān)概念,以及通過注解方式和XML配置文件兩種方式使用 Spring AOP進(jìn)行編程曲伊。 相比于 AspectJ 的面向切面編程惋耙,Spring AOP 也有一些局限性,但是已經(jīng)可以解決開發(fā)中的絕大多數(shù)問題了熊昌,如果確實(shí)遇到了 Spring AOP 解決不了的場(chǎng)景,我們依然可以在 Spring 中使用 AspectJ 來解決湿酸。

spring學(xué)習(xí)筆記和相關(guān)面試題領(lǐng)取

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婿屹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子推溃,更是在濱河造成了極大的恐慌昂利,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铁坎,死亡現(xiàn)場(chǎng)離奇詭異蜂奸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)硬萍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門扩所,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朴乖,你說我怎么就攤上這事祖屏。” “怎么了买羞?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵袁勺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我畜普,道長(zhǎng)期丰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任吃挑,我火速辦了婚禮钝荡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘儒鹿。我一直安慰自己化撕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布约炎。 她就那樣靜靜地躺著植阴,像睡著了一般蟹瘾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掠手,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天憾朴,我揣著相機(jī)與錄音,去河邊找鬼喷鸽。 笑死众雷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的做祝。 我是一名探鬼主播砾省,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼混槐!你這毒婦竟也來了编兄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤声登,失蹤者是張志新(化名)和其女友劉穎狠鸳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悯嗓,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡件舵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脯厨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铅祸。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖俄认,靈堂內(nèi)的尸體忽然破棺而出个少,到底是詐尸還是另有隱情,我是刑警寧澤眯杏,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布夜焦,位于F島的核電站,受9級(jí)特大地震影響岂贩,放射性物質(zhì)發(fā)生泄漏茫经。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一萎津、第九天 我趴在偏房一處隱蔽的房頂上張望卸伞。 院中可真熱鬧,春花似錦锉屈、人聲如沸荤傲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遂黍。三九已至终佛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雾家,已是汗流浹背铃彰。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芯咧,地道東北人牙捉。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像敬飒,于是被迫代替她去往敵國(guó)和親邪铲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容