Spring AOP使用入門案例

關(guān)于AOP的原理網(wǎng)上有很多教程,此處不再贅述,只是通過具體的案例來記錄如何使用。

一、入門案例

首先從start.spring.io上下載一個Spring Boot工程,只需要引入web依賴即可。然后我們創(chuàng)建一個Controller:

@RestController
public class HelloController {

    /*@AspectAction(name = "loglog")*/
    @GetMapping("/getHello")
    public String getHello(){
        System.out.println("執(zhí)行g(shù)etHello!");
        return "hello";
    }

}

然后創(chuàng)建一個切面:

@Aspect
@Component
public class LogAspect {

    /**
     * 前置通知
     * 在方法執(zhí)行之前執(zhí)行
     */
    @Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void before() {
        System.out.println("before");
    }

}

這個切面中最重要的就是@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))"),它說明了這是一個前置通知,并且切入點為HelloController類中的getHello方法。

到這里一個最簡單的AOP例子就能運行了,啟動程序后,訪問入口,打印信息如下:

before
執(zhí)行g(shù)etHello!

二、通知類型

總共支持五種通知類型:

@Aspect
@Component
public class LogAspect {

    /**
     * 前置通知
     * 在方法執(zhí)行之前執(zhí)行
     */
    @Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void before() {
        System.out.println("before");
    }

    /**
     * 后置通知
     * 在方法執(zhí)行之后
     */
    @After("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void after() {
        System.out.println("after");
    }

    /**
     * 返回通知
     * 在方法執(zhí)行之后,返回之前執(zhí)行
     */
    @AfterReturning("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void afterReturing(){
        System.out.println("afterReturning");
    }

    /**
     * 異常通知
     * 在方法拋出異常之后執(zhí)行
     */
    @AfterThrowing("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

}

執(zhí)行結(jié)果可以表現(xiàn)各個通知的執(zhí)行順序:

before
執(zhí)行g(shù)etHello!
afterReturning
after

還有一個環(huán)繞通知,可以代替其它四個通知,環(huán)繞通知可以實現(xiàn)其它四個通知。

    /**
     * 環(huán)繞通知
     * 能夠代替其它四種通知使用
     */
    @Around("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("前置通知!");
        try {
            // 執(zhí)行目標(biāo)方法中的邏輯
            joinPoint.proceed();
            System.out.println("返回通知!");
        } catch (Throwable throwable) {
            System.out.println("異常通知!");
        } finally {
            System.out.println("后置通知!");
        }
    }

假設(shè)同時設(shè)置了四種通知,還有環(huán)繞通知,那么執(zhí)行的順序是怎樣的呢?

前置通知!
before
執(zhí)行g(shù)etHello!
afterReturning
after
返回通知!
后置通知!

需要注意的是,環(huán)繞通知中的joinPoint.proceed();如果不寫的話,就會跳過切入點目標(biāo)方法的邏輯執(zhí)行,直接執(zhí)行環(huán)繞通知后續(xù)的通知邏輯:

前置通知!
返回通知!
后置通知!

三、切點表達(dá)式

在如上的案例中,我們發(fā)現(xiàn)如果定義了很多通知,在定義切入點表達(dá)式時寫了很多一樣的東西:

"execution(* com.example.aopdemo.controller.HelloController.getHello(..))"

其實可以在切面類中先定義一個切點表達(dá)式,然后其它通知定義的時候引用這個切點表達(dá)式即可:

@Aspect
@Component
public class LogAspect {

    /**
     * 聲明切點表達(dá)式
     */
    @Pointcut("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void pointCut(){}


    /**
     * 環(huán)繞通知
     * 能夠代替其它四種通知使用
     */
    @Around("pointCut()")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("前置通知!");
        try {
            //joinPoint.proceed();
            System.out.println("返回通知!");
        } catch (Throwable throwable) {
            System.out.println("異常通知!");
        } finally {
            System.out.println("后置通知!");
        }
    }

}

四、切點指示器

以上面的切點表達(dá)式為例:

  • execution表示匹配執(zhí)行目標(biāo)方法,還有其它類型的指示器,但用的不多;

  • *表示不關(guān)心方法的返回值;

  • ..表示不關(guān)心方法的參數(shù);

  • 如果需要多個匹配條件可以使用&& || !來表示且、或、非的關(guān)系;

    @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")
    

    其中within是和execution平行的指示器,表示匹配的切入點類型;bean是實例匹配器,只有遇到girl這個實例的時候才會執(zhí)行AOP;

五、使自定義注解

有的時候我們希望在聲明自定義注解的方法上執(zhí)行AOP的邏輯,而不是如上指定具體的位置。

首先我們需要先定義自己的注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AspectAction {
    String name() default "default";
}

然后,在切面中定義切入點為標(biāo)注這個注解的地方。

@Aspect
@Component
public class LogAspect {

    @Pointcut("@annotation(com.example.aopdemo.annotation.AspectAction)")
    public void pointCut(){}

    @Around("pointCut()")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("前置通知!");
        try {
            joinPoint.proceed();
            System.out.println("返回通知!");
        } catch (Throwable throwable) {
            System.out.println("異常通知!");
        } finally {
            System.out.println("后置通知!");
        }
    }
}

最后,只要在想使用AOP的方法之上加上自定義注解即可:

@AspectAction(name = "loglog")
@GetMapping("/getHello")
public String getHello(){
    System.out.println("執(zhí)行g(shù)etHello!");
    return "hello";
}

我們在環(huán)繞通知中,可以通過ProceedingJoinPoint來獲取如下各種信息,ProceedingJoinPoint繼承自JoinPoint,所以在其它類型的通知中也可以獲得如下內(nèi)容:

  • 方法簽名;
  • 方法入?yún)ⅲ?/li>
  • 等等,參考源代碼即可;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容