自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

輕松上手Spring AOP,掌握切面編程的核心技巧

開發(fā) 前端
本文介紹了Spring AOP的常用的切點表達式、通知注解等,我們可以利用AOP對業(yè)務邏輯的各個部分進行隔離,使得業(yè)務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高開發(fā)的效率。

Spring框架是我們使用比較多的一個框架,而AOP又是Spring的核心特性之一,本篇文章將介紹一下AOP的切點表達式、通知等特性及如何使用Spring AOP。

AOP 是什么

AOP(Aspect-Oriented Programming,面向切面編程) 是一種編程范式,旨在將橫切關注點與核心業(yè)務邏輯相分離,以提高代碼的模塊化性、可維護性和復用性。

在傳統(tǒng)的面向?qū)ο缶幊讨?,程序的功能被模塊化為類和方法,但某些功能可能會跨越多個類和方法,如日志記錄、事務管理、安全控制等,這些功能不屬于核心業(yè)務邏輯,但又必須在多個地方重復使用,導致代碼重復和耦合性增加。

AOP提供了一種機制,可以將這些橫切關注點單獨定義,并在需要的地方插入到應用程序中,而不必修改核心業(yè)務邏輯。

AspectJ

AspectJ是一個面向切面的框架,它擴展了Java語言。AspectJ定義了AOP(面向切面編程) 語法,并擁有一個專門的編譯器,用于生成遵守Java字節(jié)編碼規(guī)范的Class文件。

AspectJ可以單獨使用,也可以整合到其他框架中。當單獨使用AspectJ時,需要使用專門的編譯器ajc。AspectJ屬于靜態(tài)織入,通過修改代碼來實現(xiàn),包括編譯期織入等多種織入時機。

Spring集成AspectJ,可以在Spring中方便的使用AOP。

Spring AOP

Spring AOP核心概念主要包括以下幾個方面:

  1. 切面(Aspect):切面是模塊化橫切關注點的機制,由切入點和通知組成。在Spring AOP中,一個切面可以定義在什么時候、什么地方以及如何應用某種特定的行為到目標對象上。
  2. 連接點(Joinpoint):連接點是程序執(zhí)行過程中的一個點,例如方法的調(diào)用、字段的訪問等。在Spring AOP中,一個連接點總是代表一個方法的執(zhí)行。連接點是AOP框架可以在其上 “織入” 切面的點。
  3. 通知(Advice):通知定義了在切入點執(zhí)行時要執(zhí)行的代碼,它是增強應用到連接點上的行為。通知有多種類型,包括前置通知(Before Advice)、后置通知(After Advice) 、環(huán)繞通知(Around Advice) 、異常通知(After Throwing Advice) 和  返回通知(After Returning Advice)  。這些通知類型決定了增強在連接點上的執(zhí)行順序和方式。
  4. 切點(Pointcut):切點用于定義通知應該應用到哪些連接點上。它是一組連接點的集合,這些連接點共享相同的特性。切點表達式用于匹配連接點,從而確定哪些連接點應該接收通知。
  5. 目標對象(Target Object) :被一個或多個切面所通知的對象。也被稱為被通知(advised)對象。由于Spring AOP是通過代理模式實現(xiàn)的,因此在運行時,目標對象總是被代理對象所包裹。
  6. 織入(Weaving):織入是將切面應用到目標對象并創(chuàng)建代理對象的過程。這是AOP框架在運行時或編譯時完成的核心任務。
  7. AOP代理(AOP Proxy):AOP框架創(chuàng)建的對象,用于實現(xiàn)切面編程。在Spring中,AOP代理可以是JDK動態(tài)代理或CGLIB代理。
  8. 引入(Introduction):用于向現(xiàn)有的類添加新的接口和實現(xiàn),而不需要修改原始類的代碼。Introduction允許在不修改現(xiàn)有類結構的情況下,向類引入新的功能和行為。在 AspectJ 社區(qū)中,引入稱為類型間聲明(inter-type declaration)。

這些核心概念共同構成了AOP的基礎,使得我們能夠模塊化地處理橫切關注點,從而提高代碼的可維護性和可重用性。

切點表達式

Pointcut 表達式 是用來定義切入點的規(guī)則,它決定了哪些連接點(方法調(diào)用或方法執(zhí)行)將會被通知所影響。在 Spring AOP 中,Pointcut 表達式通常由以下幾種規(guī)則和通配符組成:

  1. execution(): 用于匹配方法執(zhí)行的連接點,它是最常用的切點指示器。它基于方法簽名進行匹配,可以指定方法的返回類型、包名、類名、方法名以及參數(shù)列表等。比如: @Pointcut("execution(* com.example.myapp.service.*.*(..))") 表示匹配com.example.myapp.service包下所有類的所有方法執(zhí)行。
  2. within(): 匹配指定類型內(nèi)的方法執(zhí)行連接點。它通常用于匹配特定包或類中的所有方法。示例:@Pointcut("within(com.example.myapp.service.*)") 表示表示匹配com.example.myapp.service包下所有類的所有方法的執(zhí)行。
  3. this(): 匹配當前代理對象為指定類型的連接點。這用于限制切點只匹配特定類型的代理對象。示例:@Pointcut("this(com.example.myapp.service.MyService)") 表示匹配當前代理對象類型為com.example.myapp.service.MyService的所有方法的執(zhí)行。
  4. target(): 匹配目標對象為制定類型的連接點。與this()不同,target()是基于目標對象類型,而不是代理類型。示例:@Pointcut("target(com.example.myapp.service.MyServiceImpl)") 表示匹配目標對象類型為com.example.myapp.service.MyServiceImpl的所有方法的執(zhí)行。
  5. args(): 匹配方法執(zhí)行時參數(shù)為特定類型的連接點。示例:@Pointcut("args(java.io.Serializable)") 表示匹配方法執(zhí)行時至少有一個參數(shù)是java.io.Serializable類型的連接點。
  6. @annotation(): 匹配執(zhí)行的方法上帶有指定注解的連接點。示例:@Pointcut("@annotation(com.example.myapp.annotation.MyAnnotation)") 表示匹配執(zhí)行的方法上帶有com.example.myapp.annotation.MyAnnotation注解的連接點。
  7. @target:用于匹配所有帶有特定注解的類或接口。 這個指示器通常與execution表達式結合使用,以進一步細化匹配條件。示例:@Pointcut("@target(com.example.annotation.MyAnnotation)") 表示匹配目標對象類型上帶有com.example.myapp.annotation.MyAnnotation注解的方法執(zhí)行。
  8. @within:匹配指定類型帶有指定注解的連接點。與within()類似,但它是基于注解而不是包或類。示例: @Pointcut("@within(com.example.myapp.annotation.MyAnnotation)") 表示匹配帶有MyAnnotation注解的類的方法執(zhí)行。
  9. bean():匹配Spring容器中特定名稱的bean的方法的執(zhí)行。示例: @Pointcut("bean(myServiceImpl)") 表示匹配Spring容器中名稱為myServiceImplbean的方法的執(zhí)行。
  10. @args():用于限制匹配的方法的參數(shù)必須有指定的注解。

帶有 @ 符的切點表達式都是需要指定注解的連接點。

這些規(guī)則可以通過邏輯運算符(如 &&、||、! )進行組合,以實現(xiàn)更復雜的 Pointcut 匹配規(guī)則。我們可以根據(jù)自己的需求,靈活地使用這些規(guī)則來定義切入點表達式,實現(xiàn)對目標方法的精確匹配和監(jiān)控。

execution()

execution() 表達式使用的比較多,最復雜的一個表達式,這里重點介紹一下。

語法結構

execution() 表達式的語法結構如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

其中,各部分的含義如下:

  • modifiers-pattern: 方法的訪問修飾符,如 public、protected 等,可以省略。
  • ret-type-pattern: 方法的返回類型,如 void、int 等。
  • declaring-type-pattern: 方法所屬的類的類型模式,可以使用通配符 * 匹配任意字符。
  • name-pattern: 方法的名稱模式,可以使用通配符 * 匹配任意字符。
  • param-pattern: 方法的參數(shù)模式,包括參數(shù)類型和個數(shù)。
  • throws-pattern: 方法拋出的異常類型。

示例

  • 所有公共方法的執(zhí)行
execution(public * *(..))
  • 名稱以 set 開頭的所有方法的執(zhí)行
execution(* set*(..))
  • AccountService 接口定義的任何方法的執(zhí)行
execution(* com.xyz.service.AccountService.*(..))
  • service 包中定義的任何方法的執(zhí)行
execution(* com.xyz.service.*.*(..))
  • service 包或其子包之一中定義的任何方法的執(zhí)行
execution(* com.xyz.service..*.*(..))
  • 執(zhí)行指定類型參數(shù)的方法
execution(* com.example.service.MyService.myMethod(String, int))

注意事項

  • 在 execution() 表達式中,通配符 * 可以用來匹配任意字符或任意個數(shù)的字符。
  • 使用 execution() 表達式時,需要注意合理地組織表達式,以確保精準地匹配目標方法。
  • 可以通過組合多個條件來更加靈活地定義切點,例如同時匹配方法的訪問修飾符、返回類型、類名、方法名等。

總的來說,execution() 方法提供了一種靈活且強大的方式來定義切點表達式,從而精確定位需要添加通知的目標方法。

通知(Advice)類型

圖片圖片

在 Spring AOP 中,通知(Advice)是在切入點(Pointcut)上執(zhí)行的代碼。Spring 提供了幾種類型的通知,每種類型都對應著在連接點執(zhí)行前、執(zhí)行后或拋出異常時執(zhí)行的不同代碼邏輯。這些通知對應著不同的注解,常用的通知注解包括:

  1. @Before: 在方法執(zhí)行之前執(zhí)行的通知。它有以下屬性:

value:要綁定的切點或者切點表達式。

argNames: 用于指定連接點表達式中方法參數(shù)的名稱,以便在通知方法中通過參數(shù)名來獲取方法參數(shù)的值。這樣可以在前置通知中訪問和處理方法參數(shù)的具體數(shù)值。該屬性即使不指定也能獲取參數(shù)。

  1. @AfterReturning: 在方法執(zhí)行成功返回結果后執(zhí)行的通知。它比 @Before注解多了2個屬性:

pointcut:作用和value屬性一樣,當指定pointcut時,會覆蓋value屬性的值。

returning:方法返回的結果將被綁定到此參數(shù)名,可以在通知中訪問方法的返回值。

  1. @AfterThrowing: 在方法拋出異常后執(zhí)行的通知。它的屬性前3個和 @AfterReturning注解一樣,多了1個屬性:

throwing:指定方法拋出的異常將被綁定到此參數(shù)名,可以在通知中訪問方法拋出的異常。

  1. @After: 在方法執(zhí)行后(無論成功或失?。﹫?zhí)行的通知。屬性同 @Before 注解。

  2. @Around: 環(huán)繞通知,能夠在方法執(zhí)行前后都可以進行操作,具有最大的靈活性。屬性同  @Before 注解。

通知的執(zhí)行順序為: @Around  ->  @Before ->  @AfterReturning(不拋異常情況) 或者  @AfterThrowing(拋異常情況)  -> @After

這些通知注解可以與 Pointcut 表達式結合使用,實現(xiàn)對目標方法的攔截和處理。通過選擇合適的通知類型,開發(fā)者可以根據(jù)需求在不同的時間點插入自定義的邏輯,實現(xiàn)對方法調(diào)用的控制和增強。

如何使用

講了那么多概念性的東西,下面來看怎么使用Spring AOP。

在Spring 中使用AOP也很簡單,主要分3步:

  1. 定義切面
  2. 定義切點
  3. 在具體通知上使用切點

準備階段

我這里使用的是Springboot 3.1.5、jdk 17,如果是Springboot低版本的可能需要引入 spring-boot-starter-aop 依賴,高版本的AOP已經(jīng)包含在spring-boot-starter-web依賴中了:

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

Spring官網(wǎng)中介紹,使用Spring AOP要在啟動類或者配置類中加上 @EnableAspectJAutoProxy 注解開啟 AspectJ 注解的支持,在我使用的這個版本中并不需要,如果你的項目中切面未生效可以嘗試使用該注解。

定義一個接口,下面用于對這個接口及其實現(xiàn)類進行攔截:

public interface AopService {

    /**
     * 兩數(shù)除法
     * @param a
     * @param b
     * @return
     */
    BigDecimal divide(BigDecimal a, BigDecimal b);

    /**
     * 兩數(shù)加法
     * @param a
     * @param b
     * @return
     */
    BigDecimal add(BigDecimal a, BigDecimal b);
}
@Service
public class MyAopServiceImpl implements AopService{

    /**
     * 兩數(shù)除法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public BigDecimal divide(BigDecimal a, BigDecimal b) {
        return a.divide(b , RoundingMode.UP);
    }

    /**
     * 兩數(shù)加法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public BigDecimal add(BigDecimal a, BigDecimal b) {
        return a.add(b);
    }
}

定義切面

新建一個類,在類上加上@Aspect 注解,標記該類為切面。

@Component
@Aspect
public class AspectComponent {
}

定義并使用切點

在切面中使用@Pointcut注解定義切點表達式,然后在通知注解中使用定義好的切點。在該示例中主要對AopService#divide()方法進行攔截。

@Component
@Aspect
public class AspectComponent {

 /**
     * 匹配AopService接口的divide方法
     */
    @Pointcut("execution(* site.suncodernote.aop.AopService.divide(..))")
    void dividePointCut(){
    }

 /**
     * 匹配AopService接口的divide方法
     */
    @Pointcut("within(site.suncodernote.aop.AopService+)")
    void withinPointCut(){
    }

 /**
     * 匹配AopService接口的add方法 或者 divide方法
     */
    @Pointcut("execution(* site.suncodernote.aop.AopService.add(..)) || execution(* site.suncodernote.aop.AopService.divide(..))")
    void addOrDividePointCut(){
    }


 @Before("dividePointCut()")
    public void beforeDivide(JoinPoint joinPoint){
        System.out.println("---------------------@Before----------------");
        printJoinPoint(joinPoint);
    }

    @After("dividePointCut()")
    public void afterDivide(JoinPoint joinPoint){
        System.out.println("---------------------@After----------------");
        printJoinPoint(joinPoint);
    }

    @AfterReturning(pointcut = "dividePointCut()" , returning = "result")
    public void afterReturningDivide(JoinPoint joinPoint , BigDecimal result){
        System.out.println("---------------------@AfterReturning----------------");
        System.out.println("返回結果="+result);
        printJoinPoint(joinPoint);
    }

    @AfterThrowing(pointcut = "dividePointCut()" , throwing = "e")
    public void afterThrowingDivide(JoinPoint joinPoint ,Exception e){
        System.out.println("---------------------@AfterThrowing----------------");
        System.out.println("異常:"+e.getMessage());
        printJoinPoint(joinPoint);
    }

    @Around("dividePointCut()")
    public Object aroundDivide(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---------------------@Around----------------");
        printJoinPoint(joinPoint);
        Object[] args = joinPoint.getArgs();
        Object result = null;
        try {
            //執(zhí)行方法
            result = joinPoint.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("返回值:"+result);
        return result;
    }

    private void printJoinPoint(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        System.out.println("方法名:"+signature.getName());
        System.out.println("方法參數(shù):"+ Arrays.toString(args));
        System.out.println();
    }
}

測試

寫個簡單的單元測試,調(diào)用AopService#divide()方法,然后看一下輸出結果。

@SpringBootTest
public class AOPTest {

    @Resource
    private AopService aopService;

    @Test
    public void testAOP() {
        BigDecimal a = new BigDecimal(1);
        BigDecimal b = new BigDecimal(2);

//        aopService.add(a, b);
        aopService.divide(a, b);
    }
}

測試結果:

---------------------@Around----------------
方法名:divide
方法參數(shù):[1, 2]

---------------------@Before----------------
方法名:divide
方法參數(shù):[1, 2]

---------------------@AfterReturning----------------
返回結果=1
方法名:divide
方法參數(shù):[1, 2]

---------------------@After----------------
方法名:divide
方法參數(shù):[1, 2]

返回值:1

從測試結果中通知執(zhí)行的順序是按照我們上面所說的執(zhí)行順序執(zhí)行的。

總結

本文介紹了Spring AOP的常用的切點表達式、通知注解等,我們可以利用AOP對業(yè)務邏輯的各個部分進行隔離,使得業(yè)務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高開發(fā)的效率。

責任編輯:武曉燕 來源: 索碼理
相關推薦

2012-02-10 10:32:33

JavaSpring

2009-08-24 09:46:40

面向切面編程AOP

2013-09-17 10:37:03

AOPAOP教程理解AOP

2024-03-21 09:51:22

Python爬蟲瀏覽網(wǎng)站

2023-11-07 16:00:25

面向切面編程開發(fā)

2024-05-21 09:55:43

AspectOrientedAOP

2011-04-26 09:33:04

SpringAOP

2020-04-23 15:59:04

SpringKafka集群

2009-12-11 17:25:00

Grub突破root

2024-06-11 00:04:00

對象AdvisorAdvice

2014-10-30 16:12:55

編程技術算法

2014-10-30 16:34:28

編程技術算法

2014-10-30 16:41:14

編程技術算法

2009-12-10 11:02:44

PHP函數(shù)eval()

2023-10-20 09:32:25

Java技術

2023-12-29 08:37:50

死鎖長事務編程式

2024-05-10 11:52:00

編程C++

2009-12-17 14:36:57

Ruby on Rai

2010-01-14 13:59:01

2010-08-02 10:06:43

DB2數(shù)據(jù)移動
點贊
收藏

51CTO技術棧公眾號