欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring面向切面編程AOP詳情

 更新時間:2022年09月23日 10:53:44   作者:旭日的芬芳  
這篇文章主要介紹了Spring面向切面編程AOP詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下

1. 面向切面編程

  • 定義:面向切面編程(AOP,Aspect Oriented Programming)是通過預(yù)編譯方式和運行期間動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)。
  • 作用:利用AOP可以對業(yè)務(wù)邏輯的各個部分進行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率。
  • 主要功能:日志記錄、性能統(tǒng)計、安全控制、事務(wù)處理、異常處理等。
  • 總結(jié):面向切面編程是希望能夠?qū)⑼ㄓ眯枨蠊δ軓牟幌嚓P(guān)的類當(dāng)中分離出來,能夠使得很多類共享一個行為,一旦發(fā)生變化,不必修改很多類,而只是修改這個行為即可。

AOP通過提供另一種思考程序結(jié)構(gòu)的方式來補充了面向?qū)ο缶幊蹋∣OP)。OOP中模塊化的基本單元是類(class),而AOP中模塊化的基本單元是切面(aspect)。可以這么理解,OOP是解決了縱向的代碼復(fù)用問題,AOP是解決了橫向的代碼復(fù)用問題。

Spring的關(guān)鍵組件之一是AOP框架。雖然Spring IOC容器不依賴于AOP,意味著如果你不想使用AOP,則可以不使用AOP,但AOP補充了Spring IOC以提供一個非常強大的中間件解決方案。

2. AOP核心概念

  • 切面(aspect):在AOP中,切面一般使用@Aspect注解來標(biāo)識。
  • 連接點(Join Point):在Spring AOP,一個連接點總是代表一次方法的執(zhí)行。
  • 增強(Advice):在連接點執(zhí)行的動作。
  • 切入點(Pointcout):說明如何匹配到連接點。引
  • 介(Introduction):為現(xiàn)有類型聲明額外的方法和屬性。
  • 目標(biāo)對象(Target Object):由一個或者多個切面建議的對象,也被稱為“建議對象”,由于Spring AOP是通過動態(tài)代理來實現(xiàn)的,這個對象永遠(yuǎn)是一個代理對象。
  • AOP代理(AOP proxy):一個被AOP框架創(chuàng)建的對象,用于實現(xiàn)切面約定(增強方法的執(zhí)行等)。在Spring Framework中,一個AOP代理是一個JDK動態(tài)代理或者CGLIB代理。
  • 織入(Weaving):連接切面和目標(biāo)對象或類型創(chuàng)建代理對象的過程。它能在編譯時(例如使用AspectJ編譯器)、加載時或者運行時完成。Spring AOP與其他的純Java AOP框架一樣是在運行時進行織入的。

Spring AOP包括以下類型的增強:

  • 前置增強(Before advice):在連接點之前運行,但不能阻止到連接點的流程繼續(xù)執(zhí)行(除非拋出異常)
  • 返回增強(After returning advice):在連接點正常完成后運行的增強(例如,方法返回沒有拋出異常)
  • 異常增強(After thorwing advice):如果方法拋出異常退出需要執(zhí)行的增強
  • 后置增強(After (finally) Advice):無論連接點是正常或者異常退出,都會執(zhí)行該增強
  • 環(huán)繞增強(Around advice):圍繞連接點的增強,例如方法的調(diào)用。環(huán)繞增強能在方法的調(diào)用之前和調(diào)用之后自定義行為。它還可以選擇方法是繼續(xù)執(zhí)行或者去縮短方法的執(zhí)行通過返回自己的值或者拋出異常。

3. AOP的實現(xiàn)

AOP的兩種實現(xiàn)方式:靜態(tài)織入(以AspectJ為代表)和動態(tài)代理(Spring AOP實現(xiàn))

AspectJ是一個采用Java實現(xiàn)的AOP框架,它能夠?qū)Υa進行編譯(在編譯期進行),讓代碼具有AspectJ的AOP功能,當(dāng)然它也可支持動態(tài)代理的方式;

Spring AOP實現(xiàn):通過動態(tài)代理技術(shù)來實現(xiàn),Spring2.0集成了AspectJ,主要用于PointCut的解析和匹配,底層的技術(shù)還是使用的Spring1.x中的動態(tài)代理來實現(xiàn)。

 Spring AOP采用了兩種混合的實現(xiàn)方式:JDK動態(tài)代理和CGLib動態(tài)代理。

JDK動態(tài)代理:Spring AOP的首選方法。每當(dāng)目標(biāo)對象實現(xiàn)一個接口時,就會使用JDK動態(tài)代理。目標(biāo)對象必須實現(xiàn)接口。

CGLIB:如果目標(biāo)對象沒有實現(xiàn)接口,則可以使用CGLIB代理。 

4. Spring 對AOP支持

Spring可以使用兩種方式來實現(xiàn)AOP:基于注解式配置和基于XML配置

下面介紹基于注解配置的形式

4.1 支持@Aspect

如果是Spring Framework,需要使用aspectjweaver.jar包,然后創(chuàng)建我們自己的AppConfig,如下,并加上@EnableAspectJAutoProxy注解開啟AOP代理自動配置(Spring Boot默認(rèn)是開啟的,則不需要增加配置),

如下:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
 
}

4.2 聲明一個切面

@Aspect //告訴Spring 這是一個切面
@Component  //交給Spring容器管理
public class MyAspect {
 
}

可以使用@Aspect來定義一個類作為切面,但是這樣,該類并不會自動被Spring加載,還是需要加上@Component注解

4.3 聲明一個切入點

一個切入點的生命包含兩個部分:一個包含名稱和任何參數(shù)的簽名和一個切入點的表達式,這個表達式確定了我們對哪些方法的執(zhí)行感興趣。

我們以攔截Controller層中的MyController中的test方法為例子,代碼如下:

@RestController
@RequestMapping("/my")
public class MyController {
 
    @GetMapping("/test")
    public void test() {
        System.out.println("test 方法");
    }
}

下面定義一個名為controller的切入點,該切入點與上述的test方法相匹配,切入點需要用@Pointcut注解來標(biāo)注,如下:

  //表達式
  @Pointcut("execution (public * com.yc.springboot.controller.MyController.test())")
  public void controller(){}; //簽名

切入點表達式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(方法修飾符(可選) 返回類型 類路徑 方法名 參數(shù) 異常模式(可選))

AspectJ描述符如下:

AspectJ描述符描述
arg()限制連接點匹配參數(shù)為指定類型的執(zhí)行方法
@args()限制連接點匹配參數(shù)由指定注解標(biāo)注的執(zhí)行方法
execution()用于匹配是連接點的執(zhí)行方法
this()限制連接點匹配的AOP代理的bean引用為指定類型的類
target限制連接點匹配目標(biāo)對象為指定類型的類
@target()限制連接點匹配特定的執(zhí)行對象,這些對象對應(yīng)的類要具有指定類型的注解
within()限制連接點匹配指定的類型
@within()限制連接點匹配指定注解所標(biāo)注的類型
@annotationn限定匹配帶有指定注解的連接點

常用的主要是:execution()

AspectJ類型匹配的通配符:

within()限制連接點匹配指定的類型
@within()限制連接點匹配指定注解所標(biāo)注的類型
@annotationn限定匹配帶有指定注解的連接點

常用的匹配規(guī)則:

表達式內(nèi)容
execution(public * *(..))  匹配所有public方法
execution(* set*(..))匹配所有方法名開頭為set的方法
execution(* com.xyz.service.AccountService.*(..))匹配AccountService下的所有方
execution(* com.xyz.service.*.*(..))匹配service包下的所有方法
execution(* com.xyz.service..*.*(..))匹配service包或其子包下的所有方法
@annotation(org.springframework.transaction.annotation.Transactional)匹配所有打了@Transactional注解的方法
bean(*Service)匹配命名后綴為Service的類的方法

4.4 聲明增強

增強與切點表達式相關(guān)聯(lián),并且在與切點匹配的方法之前、之后或者前后執(zhí)行。

在3當(dāng)中已經(jīng)對各類增強做了紹,這里就不詳細(xì)展開了,下面直接羅列了各種增強的聲明,用于攔截MyController中的各個方法

 
@Aspect //告訴Spring 這是一個切面
@Component  //告訴Spring容器需要管理該對象
public class MyAspect {
 
    //通過規(guī)則確定哪些方法是需要增強的
    @Pointcut("execution (public * com.yc.springboot.controller.MyController.*(..))")
    public void controller() {
    }
 
    //前置增強
    @Before("controller()")
    public void before(JoinPoint joinPoint) {
        System.out.println("before advice");
    }
 
    //返回增強
    @AfterReturning(
            pointcut = "controller()",
            returning = "retVal"
    )
    public void afterReturning(Object retVal) {
        System.out.println("after returning advice, 返回結(jié)果 retVal:" + retVal);
    }
 
    //異常增強
    @AfterThrowing(
            pointcut = "controller()",
            throwing = "ex"
    )
    public void afterThrowing(Exception ex) {
        System.out.println("after throwing advice, 異常 ex:" + ex.getMessage());
    }
 
    //后置增強
    @After("controller()")
    public void after(JoinPoint joinPoint) {
        System.out.println("after advice");
    }
 
    //環(huán)繞增強
    @Around("controller()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("before advice");
        //相當(dāng)于是before advice
        Object reVal = null;
        try {
            reVal = joinPoint.proceed();
        } catch (Exception e) {
            //相當(dāng)于afterthrowing advice
            System.out.println("afterthrowing advice");
        }
        //相當(dāng)于是after advice
        System.out.println("after advice");
        return reVal;
    }
}

需要注意的是:

  • 在返回增強中,我們需要給@AfterReturing設(shè)置returning的值,且需要與方法的參數(shù)名一致,用于表示業(yè)務(wù)方法的返回值
  • 在異常增強中,需要給@AfterThrowing設(shè)置throwing的值,且需要與方法的參數(shù)名一致,用于表示業(yè)務(wù)方法產(chǎn)生的異常在環(huán)繞增強中,參數(shù)為ProceedingJoinPoint類型,它是JoinPoint的子接口,我們需要在這個方法中手動調(diào)用其proceed方法來觸發(fā)業(yè)務(wù)方法
  • 在所有的增強方法中都可以申明第一個參數(shù)為JoinPoint(注意的是,環(huán)繞增強是使用ProceedingJoinPoint來進行申明,它實現(xiàn)了JoinPoint接口)
  • JoinPoint接口提供了幾個有用的方法 :
    • getArgs():返回這個方法的參數(shù)
    • getThis():返回這個代理對象
    • getTarget():返回目標(biāo)對象(被代理的對象)
    • getSignature():返回被增強方法的描述
    • toString():打印被增強方法的有用描述

下面為Mycontroller測試類:

@RestController
@RequestMapping("/my")
public class MyController {
 
    @GetMapping("/testBefore")
    public void testBefore() {
        System.out.println("testBefore 業(yè)務(wù)方法");
    }
 
    @GetMapping("/testAfterReturning")
    public String testAfterReturning() {
        System.out.println("testAfterReturning 業(yè)務(wù)方法");
        return "我是一個返回值";
    }
 
    @GetMapping("/testAfterThrowing")
    public void testAfterThrowing() {
        System.out.println("testAfterThrowing 業(yè)務(wù)方法");
        int a = 0;
        int b = 1 / a;
    }
 
    @GetMapping("/testAfter")
    public void testAfter() {
        System.out.println("testAfter 業(yè)務(wù)方法");
    }
 
    @GetMapping("/around")
    public void around() {
        System.out.println("around 業(yè)務(wù)方法");
    }
}

5. 用AOP實現(xiàn)日志攔截

5.1 一般的實現(xiàn)

打印日志是AOP的一個常見應(yīng)用場景,我們可以對Controller層向外提供的接口做統(tǒng)一的日志攔截,用日志記錄請求參數(shù)、返回參數(shù)、請求時長以及異常信息,方便我們線上排查問題,下面是核心類LogAspect的實現(xiàn)

/**
 * 日志的切面
 */
@Aspect
@Component
public class LogAspect {
 
    @Resource
    private IdWorker idWorker;
 
    @Pointcut("execution (public * com.yc.core.controller.*.*(..))")
    public void log(){}
 
    /**
     * 使用環(huán)繞增強實現(xiàn)日志打印
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("log()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //獲得執(zhí)行方法的類和名稱
        String className = joinPoint.getTarget().getClass().getName();
        //獲得方法名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();
        //獲得參數(shù)
        Object[] args = joinPoint.getArgs();
        long requestId = idWorker.nextId();
        //打印參數(shù)
        LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",params:" + JSONObject.toJSONString(args));
        long startTime = System.currentTimeMillis();
        //執(zhí)行業(yè)務(wù)方法
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Exception e) {
            LogHelper.writeErrLog(className, methodName, "requestId:" + requestId + ",異常啦:" + LogAspect.getStackTrace(e));
        }
        long endTime = System.currentTimeMillis();
        //打印結(jié)果
        LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",耗時:" + (endTime - startTime) +  "ms,result:" + JSONObject.toJSONString(result));
        //返回
        return result;
    }
 
    /**
     * 獲取異常的堆棧信息
     * @param throwable
     * @return
     */
    public static String getStackTrace(Throwable throwable)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try
        {
            throwable.printStackTrace(pw);
            return sw.toString();
        } finally
        {
            pw.close();
        }
    }
}
  • 在proceed()方法之前,相當(dāng)于前置增強,收集類名、方法名、參數(shù),記錄開始時間,生成requestId
  • 在proceed()方法之后,相當(dāng)于后置增強,并能獲取到返回值,計算耗時
  • 在前置增強時,生成requestId,用于串聯(lián)多條日志
  • 使用try、catch包裹proceed()方法,在catch中記錄異常日志
  • 提供了getStackTrace方法獲取異常的堆棧信息,便于排查報錯詳細(xì)情況

5.2 僅攔截需要的方法

但是上面的日志是針對所有controller層中的方法進行了日志攔截,如果我們有些方法不想進行日志輸出,比如文件上傳的接口、大量數(shù)據(jù)返回的接口,這個時候定義切入點的時候可以使用@annotation描述符來匹配加了特定注解的方法,步驟如下:

1. 先定義一個日志注解Log

/**
 * 自定義日志注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}

2.用@annotation定義切入點

    @Pointcut("@annotation(com.yc.core.annotation.Log)")
    public void logAnnotation(){}

3.在想做日志輸出的方法上使用注解Log

    @Log
    @PostMapping(value = "testannotation")
    public AOPTestVO testannotation(@RequestBody AOPTestDTO aopTestDTO) {
        AOPTestVO aopTestVO = new AOPTestVO();
        aopTestVO.setCode(1);
        aopTestVO.setMsg("哈哈哈");
        return aopTestVO;
    }

這樣,我們就可以自定義哪些方法需要日志輸出了

5.3 requestId傳遞

后來有同事提到,如果這是針對Controller層的攔截,但是Service層也有自定義的日志輸出,怎么在Service層獲取到上述的requestId呢?

其實就是我們攔截之后,是否可以針對方法的參數(shù)進行修改呢?其實注意是看

result = joinPoint.proceed();

我們發(fā)現(xiàn)ProceedingJoinPoint還有另外一個帶有參數(shù)的proceed方法,定義如下:

public Object proceed(Object[] args) throws Throwable;

我們可以利用這個方法,在環(huán)繞增強中去增加requestId,這樣后面的增強方法或業(yè)務(wù)方法中就能獲取到這個requestId了。

首先,我們先定義一個基類AOPBaseDTO,只有一個屬性requestId

@Data
@ApiModel("aop參數(shù)基類")
public class AOPBaseDTO {
    @ApiModelProperty(value = "請求id", hidden = true)
    private long requestId;
}

然后我們讓Controller層接口的參數(shù)AOPTestDTO繼承上述AOPBaseDTO,如下:

@Data
@ApiModel("aop測試類")
public class AOPTestDTO extends AOPBaseDTO{
 
    @ApiModelProperty(value = "姓名")
    private String name;
 
    @ApiModelProperty(value = "年齡")
    private int age;
}

最后在環(huán)繞的增強中添加上requestId,如下:

    /**
     * 使用環(huán)繞增強實現(xiàn)日志打印
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("logAnnotation()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //獲得執(zhí)行方法的類和名稱
        String className = joinPoint.getTarget().getClass().getName();
        //獲得方法名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();
        //獲得參數(shù)
        Object[] args = joinPoint.getArgs();
        long requestId = idWorker.nextId();
        for(int i = 0; i < args.length; i++) {
            if (args[i] instanceof AOPBaseDTO) {
                //增加requestId
                ((AOPBaseDTO) args[i]).setRequestId(requestId);
            }
        }
        //打印參數(shù)
        LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",params:" + JSONObject.toJSONString(args));
        long startTime = System.currentTimeMillis();
        //執(zhí)行業(yè)務(wù)方法
        Object result = null;
        try {
            result = joinPoint.proceed(args);
        } catch (Exception e) {
            LogHelper.writeErrLog(className, methodName, "requestId:" + requestId + ",異常啦:" + LogAspect.getStackTrace(e));
        }
        long endTime = System.currentTimeMillis();
        //打印結(jié)果
        LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",耗時:" + (endTime - startTime) +  "ms,result:" + JSONObject.toJSONString(result));
        //返回
        return result;
    }

我們運行起代碼,訪問一下,下面是運行結(jié)果:

可以看到,我們的業(yè)務(wù)方法中已經(jīng)能獲取到requestId,如果Service層需要,可以通過傳遞AOPTestDTO,從中獲取。

5.4 關(guān)于增強執(zhí)行的順序

  • 1. 針對不同類型的增強,順序固定的,比如before在after前面
  • 2. 針對同一切面的相同類型的增強,根據(jù)定義先后順序依次執(zhí)行
  • 3. 針對不同切面的相同增強,可以通過使我們的切面實現(xiàn)Ordered接口,重寫getOrder方法,返回值最小,優(yōu)先級越高。注意的是before和after的相反的,before的優(yōu)先級越高越早執(zhí)行,after的優(yōu)先級越高,越晚執(zhí)行

6. 思考

  • 1. 代理對象是什么時候創(chuàng)建的?
  • 2. 當(dāng)存在多個不同類型增強時,執(zhí)行順序是怎么保證的?
  • 3. 真正的業(yè)務(wù)方法是什么時候調(diào)用的,怎么做到只調(diào)用一次?

到此這篇關(guān)于Spring面向切面編程AOP詳情的文章就介紹到這了,更多相關(guān)Spring面向切面編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java編寫猜數(shù)字游戲

    java編寫猜數(shù)字游戲

    這篇文章主要為大家詳細(xì)介紹了java編寫猜數(shù)字游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • 排序算法的Java實現(xiàn)全攻略

    排序算法的Java實現(xiàn)全攻略

    這篇文章主要介紹了排序算法的Java實現(xiàn),包括Collections.sort()的使用以及各種經(jīng)典算法的Java代碼實現(xiàn)方法總結(jié),超級推薦!需要的朋友可以參考下
    2015-08-08
  • maven中springboot-maven-plugin的5種打包方式

    maven中springboot-maven-plugin的5種打包方式

    本文主要介紹了maven中springboot-maven-plugin的5種打包方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-09-09
  • logback FixedWindowRollingPolicy固定窗口算法重命名文件滾動策略

    logback FixedWindowRollingPolicy固定窗口算法重命名文件滾動策略

    這篇文章主要介紹了FixedWindowRollingPolicy根據(jù)logback 固定窗口算法重命名文件滾動策略源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • Java集合的總體框架相關(guān)知識總結(jié)

    Java集合的總體框架相關(guān)知識總結(jié)

    今天帶大家學(xué)習(xí)Java集合框架的相關(guān)知識,文中有非常詳細(xì)的圖文介紹,對正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • Java實現(xiàn)用位運算維護狀態(tài)碼

    Java實現(xiàn)用位運算維護狀態(tài)碼

    位運算是一種非常高效的運算方式,在算法考察中比較常見,那么業(yè)務(wù)代碼中我們?nèi)绾问褂梦贿\算呢,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧
    2024-03-03
  • Jmeter跨線程組共享cookie過程圖解

    Jmeter跨線程組共享cookie過程圖解

    這篇文章主要介紹了Jmeter跨線程組共享cookie過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-07-07
  • UniApp?+?SpringBoot?實現(xiàn)支付寶支付和退款功能

    UniApp?+?SpringBoot?實現(xiàn)支付寶支付和退款功能

    這篇文章主要介紹了UniApp?+?SpringBoot?實現(xiàn)支付寶支付和退款功能,基本的?SpringBoot?的腳手架,可以去IDEA?自帶的快速生成腳手架插件,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2022-06-06
  • Java深入講解static操作符

    Java深入講解static操作符

    static關(guān)鍵字基本概念我們可以一句話來概括:方便在沒有創(chuàng)建對象的情況下來進行調(diào)用。也就是說:被static關(guān)鍵字修飾的不需要創(chuàng)建對象去調(diào)用,直接根據(jù)類名就可以去訪問,讓我們來了解一下你可能還不知道情況
    2022-07-07
  • 一文搞清楚Java中Comparable和Comparator的區(qū)別

    一文搞清楚Java中Comparable和Comparator的區(qū)別

    Java中的Comparable和Comparator都是用于集合排序的接口,但它們有明顯的區(qū)別,文中通過一些實例代碼詳細(xì)介紹了Java中Comparable和Comparator的區(qū)別,感興趣的同學(xué)跟著小編一起學(xué)習(xí)吧
    2023-05-05

最新評論