SpringBoot如何使用AOP

AOP主要實現(xiàn)的目的是針對業(yè)務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。其與設計模式完成的任務差不多,是提供另一種角度來思考程序的結構,來彌補面向對象編程的不足,這個可能是面試中經(jīng)常提到的問題,同時它也是Spring框架中一個重大的特性,對于我們開發(fā)中最常見的可能就是日志記錄,事務處理,異常處理等等。。。

proxy.png


本文將給大家介紹如何在SpringBoot中使用以及AOP的相關知識點

一、AOP知識點

1、原理

AOP是通過動態(tài)代理實現(xiàn)的,動態(tài)代理又分為兩個部分:JDK動態(tài)代理和CGLIB動態(tài)代理,AOP功能的使用還是比較簡單的,把相關bean注入到Spring容器中,編寫好相應的Aspect類即可,以下兩點需要記?。?/p>

1、AOP基于動態(tài)代理模式
2、AOP是方法級別

對代理模式不熟悉的可以參考我的這篇文章:JAVA代理模式

2、AOP術語

  • 切面(Aspect)

一個關注點的模塊化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關于橫切關注點的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式來實現(xiàn)

  • 連接點(JoinPoint)

在程序執(zhí)行過程中某個特定的點,比如某方法調(diào)用的時候或者處理異常的時候。在SpringAOP中,一個連接點總是表示一個方法的執(zhí)行

  • 通知(Advice)

在切面的某個特定的連接點上執(zhí)行的動作。其中包括了AroundBeforeAfter等不同類型的通知。許多AOP框架(包括Spring)都是以攔截器做通知模型,并維護一個以連接點為中心的攔截器鏈

  • 切入點(PointCut)

匹配連接點的斷言。通知和一個切入點表達式關聯(lián),并在滿足這個切入點的連接點上運行(例如,當執(zhí)行某個特定名稱的方法時),切入點表達式如何和連接點匹配是AOP的核心:Spring缺省使用AspectJ切入點語法

  • 引入(Intorduction)

用來給一個類型聲明額外的方法或屬性(也被稱為連接類型聲明)。Spring允許引入新的接口(以及一個對應的實現(xiàn))到任何被代理的對象。例如,你可以使用引入來使一個bean實現(xiàn)接口,以便簡化緩存機制

  • 目標對象(Target Object)

被一個或者多個切面所通知的對象。也被稱做被通知對象。既然Spring AOP是通過運行時代理實現(xiàn)的,這個對象永遠是一個被代理對象

  • AOP代理(Aop proxy)

AOP框架創(chuàng)建的對象,用來實現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中,AOP代理可以是JDK動態(tài)代理或者CGLIB代理

  • 植入(weaving)

把切面連接到其它的應用程序類型或者對象上,并創(chuàng)建一個被通知的對象。這些可以在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其他純Java AOP框架一樣,在運行時完成織入

二、常用切入點指示符

1、execution表達式

用于匹配方法執(zhí)行的連接點,屬于方法級別

語法
execution(修飾符 返回值類型 方法名(參數(shù))異常)

語法參數(shù) 描述
修飾符 可選,如public,protected,寫在返回值前,任意修飾符填*號就可以
返回值類型 必選,可以使用*來代表任意返回值
方法名 必選,可以用*來代表任意方法
參數(shù) ()代表是沒有參數(shù),(..)代表是匹配任意數(shù)量,任意類型的參數(shù),當然也可以指定類型的參數(shù)進行匹配,如要接受一個String類型的參數(shù),則(java.lang.String), 任意數(shù)量的String類型參數(shù):(java.lang.String..)等等。。。
異常 可選,語法:throws 異常,異常是完整帶包名,可以是多個,用逗號分隔

符號

符號 描述
* 匹配任意字符
.. 匹配多個包或者多個參數(shù)
+ 表示類及其子類

條件符

符號 描述
&&、and
||
!

案例

  • 攔截com.gj.web包下的所有子包里的任意類的任意方法
    execution(* com.gj.web..*.*(..))
  • 攔截com.gj.web.api.Test2Controller下的任意方法
    execution(* com.gj.web.api.Test2Controller.*(..))
  • 攔截任何修飾符為public的方法
    execution(public * * (..))
  • 攔截com.gj.web下的所有子包里的以ok開頭的方法
    execution(* com.gj.web..*.ok*(..))
    更多用法大家可以根據(jù)語法自行設計,本文不在進行舉例

2、@annotation

根據(jù)所應用的注解對方法進行過濾
語法
@annotation(注解全路徑)
實例
對用了com.gj.annotations.Test注解的所有方法進行攔截
@annotation(com.gj.annotations.Test)

3、Within

根據(jù)類型(比如接口、類名或者包名過濾方法)進行攔截
語法
within(typeName)
示例

  • 對com.gj.web下的所有子包的所有方法進行攔截
    within(com.gj.web..*)
    更多用法可以根據(jù)語法自行設計

4、@Within

用于匹配所有持有指定注解類型內(nèi)的方法,與within是有區(qū)別的,within是用于匹配指定類型內(nèi)的方法執(zhí)行,而@within是指定注解類型內(nèi)的方法

5、bean

Spring AOP擴展的,AspectJ沒有對于它的指示符,用于匹配特定名稱的bean對象的執(zhí)行方法
語法
bean(beanName)
示例
bean(testController)

三、AOP通知

在切面類中需要定義切面方法用于響應響應的目標方法,切面方法即為通知方法,通知方法需要用注解標識,AspectJ支持5種類型的通知注解

注解 描述
@Before 前置通知, 在方法執(zhí)行之前執(zhí)行
@After 后置通知, 在方法執(zhí)行之后執(zhí)行
@AfterReturn 返回通知, 在方法返回結果之后執(zhí)行
@AfterThrowing 異常通知, 在方法拋出異常之后
@Around 環(huán)繞通知,圍繞方法的執(zhí)行

1、@Before

示例

    @Before("testCut()")
    public void cutProcess(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP開始攔截, 當前攔截的方法名: " + method.getName());
    }

2、@After

示例

    @After("testCut()")
    public void after(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP執(zhí)行的方法 :"+method.getName()+" 執(zhí)行完了");
    }

3、@AfterReturn

其中value表示切點方法,returning表示返回的結果放到result這個變量中
示例

    /**
     * returning屬性指定連接點方法返回的結果放置在result變量中
     * @param joinPoint 連接點
     * @param result 返回結果
     */
    @AfterReturning(value = "testCut()",returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP攔截的方法執(zhí)行成功, 進入返回通知攔截, 方法名為: "+method.getName()+", 返回結果為: "+result.toString());
    }

4、@AfterThrowing

其中value表示切點方法,throwing表示異常放到e這個變量
示例

    @AfterThrowing(value = "testCut()", throwing = "e")
    public void afterThrow(JoinPoint joinPoint, Exception e) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP進入方法異常攔截, 方法名為: " + method.getName() + ", 異常信息為: " + e.getMessage());
    }

5、Around

示例

    @Around("testCut()")
    public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("注解方式AOP攔截開始進入環(huán)繞通知.......");
            Object proceed = joinPoint.proceed();
            System.out.println("準備退出環(huán)繞......");
            return proceed;
    }

四、SpringBoot中使用

1、導入依賴

common和swagger是附加的,讀者不用必須的哈

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>cn.gjing</groupId>
    <artifactId>tools-common</artifactId>
    <version>1.0.4</version>
</dependency>
<dependency>
    <groupId>cn.gjing</groupId>
    <artifactId>tools-starter-swagger</artifactId>
    <version>1.0.9</version>
</dependency>

2、創(chuàng)建注解

注解用于進行AOP攔截

/**
 * @author Gjing
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}

2、創(chuàng)建一個接口

/**
 * @author Gjing
 **/
@RestController
@RequestMapping("test1")
public class TestController {

    @GetMapping("/ok")
    @ApiOperation(value = "測試1", httpMethod = "GET")
    @ApiImplicitParam(name = "id", value = "id值", dataType = "int", paramType = "query")
    @Test
    @NotNull
    public String test2(Integer id) {
        return "ok";
    }

}

3、創(chuàng)建切面類

/**
 * @author Gjing
 **/
@Aspect
@Component
public class TestAspect {

    @Pointcut("@annotation(com.gj.annotations.Test)")
    public void testCut() {

    }

    @Before("testCut()")
    public void cutProcess(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP開始攔截, 當前攔截的方法名: " + method.getName());
    }

    @After("testCut()")
    public void after(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP執(zhí)行的方法 :"+method.getName()+" 執(zhí)行完了");
    }


    @Around("testCut()")
    public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("注解方式AOP攔截開始進入環(huán)繞通知.......");
            Object proceed = joinPoint.proceed();
            System.out.println("準備退出環(huán)繞......");
            return proceed;
    }

    /**
     * returning屬性指定連接點方法返回的結果放置在result變量中
     * @param joinPoint 連接點
     * @param result 返回結果
     */
    @AfterReturning(value = "testCut()",returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP攔截的方法執(zhí)行成功, 進入返回通知攔截, 方法名為: "+method.getName()+", 返回結果為: "+result.toString());
    }

    @AfterThrowing(value = "testCut()", throwing = "e")
    public void afterThrow(JoinPoint joinPoint, Exception e) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("注解方式AOP進入方法異常攔截, 方法名為: " + method.getName() + ", 異常信息為: " + e.getMessage());
    }

}

4、啟動運行

  • 正常情況
    y.png
  • 異常情況
    error.png

本文到此就結束了,篇幅過長,如果有哪里有些錯的地方歡迎各位評論區(qū)留言,項目Demo源碼地址:SpringBoot-Demo

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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