日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

SpringAOP基于注解方式實現和細節

作者:W-琑 更新時間: 2024-03-06 編程語言

目錄

1.SpringAOP底層技術組成

2.初步實現

1)添加依賴

2)準備接口

3)純凈實現類

4)聲明切面類

5)開啟aspectj注解支持

6)測試效果

3.獲取通知細節信息

1)JoinPoint接口

4.切點表達式語法

5.重用/提取切點表達式

1)重用切點表達式的優點

2)同一類內部引用

3)不同類中引用

6.環繞通知

7.切面優先級設置

小結


1.SpringAOP底層技術組成

  • 動態代理(InvocationHandler):JDK原生的實現方式,需要被代理的目標類必須實現接口。因為這個技術要求代理對象和目標對象實現同樣的接口(兄弟兩個拜把子模式)。
  • cglib:通過繼承被代理的目標類(認干爹模式)實現代理,所以不需要目標類實現接口。
  • AspectJ:早期的AOP實現的框架,SpringAOP借用了AspectJ中的AOP注解。

2.初步實現

1)添加依賴

<!-- spring-aspects會幫我們傳遞過來aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.0.6</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.6</version>
</dependency>

2)準備接口

public interface Calculator {
    
    int add(int i, int j);
    
    int sub(int i, int j);
    
    int mul(int i, int j);
    
    int div(int i, int j);
    
}

3)純凈實現類

package com.atguigu.proxy;


/**
 * 實現計算接口,單純添加 + - * / 實現! 摻雜其他功能!
 */
@Component
public class CalculatorPureImpl implements Calculator {
    
    @Override
    public int add(int i, int j) {
    
        int result = i + j;
    
        return result;
    }
    
    @Override
    public int sub(int i, int j) {
    
        int result = i - j;
    
        return result;
    }
    
    @Override
    public int mul(int i, int j) {
    
        int result = i * j;
    
        return result;
    }
    
    @Override
    public int div(int i, int j) {
    
        int result = i / j;
    
        return result;
    }
}

4)聲明切面類

package com.atguigu.advice;

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

// @Aspect表示這個類是一個切面類
@Aspect
// @Component注解保證這個切面類能夠放入IOC容器
@Component
public class LogAspect {
        
    // @Before注解:聲明當前方法是前置通知方法
    // value屬性:指定切入點表達式,由切入點表達式控制當前通知方法要作用在哪一個目標方法上
    @Before(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogBeforeCore() {
        System.out.println("[AOP前置通知] 方法開始了");
    }
    
    @AfterReturning(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogAfterSuccess() {
        System.out.println("[AOP返回通知] 方法成功返回了");
    }
    
    @AfterThrowing(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogAfterException() {
        System.out.println("[AOP異常通知] 方法拋異常了");
    }
    
    @After(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogFinallyEnd() {
        System.out.println("[AOP后置通知] 方法最終結束了");
    }
    
}

5)開啟aspectj注解支持

a.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:context="http://www.springframework.org/schema/context"
       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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 進行包掃描-->
    <context:component-scan base-package="com.atguigu" />
    <!-- 開啟aspectj框架注解支持-->
    <aop:aspectj-autoproxy />
</beans>

b.配置類方式

@Configuration
@ComponentScan(basePackages = "com.atguigu")
//作用等于 <aop:aspectj-autoproxy /> 配置類上開啟 Aspectj注解支持!
@EnableAspectJAutoProxy
public class MyConfig {
}

6)測試效果

//@SpringJUnitConfig(locations = "classpath:spring-aop.xml")
@SpringJUnitConfig(value = {MyConfig.class})
public class AopTest {

    @Autowired
    private Calculator calculator;

    @Test
    public void testCalculator(){
        calculator.add(1,1);
    }
}

輸出結果

"C:\Program Files\Java\jdk-17\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2022.3.2\lib\idea_rt.jar=65511:D:\Program Files\JetBrains\IntelliJ IDEA 2022.3.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Users\Jackiechan\.m2\repository\org\junit\platform\junit-platform-launcher\1.3.1\junit-platform-launcher-1.3.1.jar;C:\Users\Jackiechan\.m2\repository\org\apiguardian\apiguardian-api\1.0.0\apiguardian-api-1.0.0.jar;C:\Users\Jackiechan\.m2\repository\org\junit\platform\junit-platform-engine\1.3.1\junit-platform-engine-1.3.1.jar;C:\Users\Jackiechan\.m2\repository\org\junit\platform\junit-platform-commons\1.3.1\junit-platform-commons-1.3.1.jar;C:\Users\Jackiechan\.m2\repository\org\opentest4j\opentest4j\1.1.1\opentest4j-1.1.1.jar;C:\Users\Jackiechan\.m2\repository\org\junit\jupiter\junit-jupiter-engine\5.3.1\junit-jupiter-engine-5.3.1.jar;C:\Users\Jackiechan\.m2\repository\org\junit\jupiter\junit-jupiter-api\5.3.1\junit-jupiter-api-5.3.1.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2022.3.2\lib\idea_rt.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2022.3.2\plugins\junit\lib\junit5-rt.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2022.3.2\plugins\junit\lib\junit-rt.jar;D:\javaprojects\backend-engineering\part01-spring\spring-aop-annotation\target\test-classes;D:\javaprojects\backend-engineering\part01-spring\spring-aop-annotation\target\classes;D:\repository\org\springframework\spring-context\6.0.6\spring-context-6.0.6.jar;D:\repository\org\springframework\spring-beans\6.0.6\spring-beans-6.0.6.jar;D:\repository\org\springframework\spring-core\6.0.6\spring-core-6.0.6.jar;D:\repository\org\springframework\spring-jcl\6.0.6\spring-jcl-6.0.6.jar;D:\repository\org\springframework\spring-expression\6.0.6\spring-expression-6.0.6.jar;D:\repository\org\junit\jupiter\junit-jupiter-api\5.3.1\junit-jupiter-api-5.3.1.jar;D:\repository\org\apiguardian\apiguardian-api\1.0.0\apiguardian-api-1.0.0.jar;D:\repository\org\opentest4j\opentest4j\1.1.1\opentest4j-1.1.1.jar;D:\repository\org\junit\platform\junit-platform-commons\1.3.1\junit-platform-commons-1.3.1.jar;D:\repository\org\springframework\spring-test\6.0.6\spring-test-6.0.6.jar;D:\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;D:\repository\mysql\mysql-connector-java\8.0.25\mysql-connector-java-8.0.25.jar;D:\repository\com\google\protobuf\protobuf-java\3.11.4\protobuf-java-3.11.4.jar;D:\repository\com\alibaba\druid\1.2.8\druid-1.2.8.jar;D:\repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\repository\org\springframework\spring-aop\6.0.6\spring-aop-6.0.6.jar;D:\repository\org\springframework\spring-aspects\6.0.6\spring-aspects-6.0.6.jar;D:\repository\org\aspectj\aspectjweaver\1.9.9.1\aspectjweaver-1.9.9.1.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 com.atguigu.test.AopTest,testCalculator
[AOP前置通知] 方法開始了
[AOP返回通知] 方法成功返回了
[AOP后置通知] 方法最終結束了

3.獲取通知細節信息

1)JoinPoint接口

需要獲取方法簽名、傳入的實參等信息時,可以在通知方法聲明JoinPoint類型的形參。

  • 要點1:JoinPoint 接口通過 getSignature() 方法獲取目標方法的簽名(方法聲明時的完整信息)
  • 要點2:通過目標方法簽名對象獲取方法名
  • 要點3:通過 JoinPoint 對象獲取外界調用目標方法時傳入的實參列表組成的數組
// @Before注解標記前置通知方法
// value屬性:切入點表達式,告訴Spring當前通知方法要套用到哪個目標方法上
// 在前置通知方法形參位置聲明一個JoinPoint類型的參數,Spring就會將這個對象傳入
// 根據JoinPoint對象就可以獲取目標方法名稱、實際參數列表
@Before(value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))")
public void printLogBeforeCore(JoinPoint joinPoint) {
    
    // 1.通過JoinPoint對象獲取目標方法簽名對象
    // 方法的簽名:一個方法的全部聲明信息
    Signature signature = joinPoint.getSignature();
    
    // 2.通過方法的簽名對象獲取目標方法的詳細信息
    String methodName = signature.getName();
    System.out.println("methodName = " + methodName);
    
    int modifiers = signature.getModifiers();
    System.out.println("modifiers = " + modifiers);
    
    String declaringTypeName = signature.getDeclaringTypeName();
    System.out.println("declaringTypeName = " + declaringTypeName);
    
    // 3.通過JoinPoint對象獲取外界調用目標方法時傳入的實參列表
    Object[] args = joinPoint.getArgs();
    
    // 4.由于數組直接打印看不到具體數據,所以轉換為List集合
    List<Object> argList = Arrays.asList(args);
    
    System.out.println("[AOP前置通知] " + methodName + "方法開始了,參數列表:" + argList);
}

2)方法返回值

在返回通知中,通過@AfterReturning注解的returning屬性獲取目標方法的返回值!

// @AfterReturning注解標記返回通知方法
// 在返回通知中獲取目標方法返回值分兩步:
// 第一步:在@AfterReturning注解中通過returning屬性設置一個名稱
// 第二步:使用returning屬性設置的名稱在通知方法中聲明一個對應的形參
@AfterReturning(
        value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
        returning = "targetMethodReturnValue"
)
public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP返回通知] "+methodName+"方法成功結束了,返回值是:" + targetMethodReturnValue);
}

3)異常對象捕捉

在異常通知中,通過@AfterThrowing注解的throwing屬性獲取目標方法拋出的異常對象

// @AfterThrowing注解標記異常通知方法
// 在異常通知中獲取目標方法拋出的異常分兩步:
// 第一步:在@AfterThrowing注解中聲明一個throwing屬性設定形參名稱
// 第二步:使用throwing屬性指定的名稱在通知方法聲明形參,Spring會將目標方法拋出的異常對象從這里傳給我們
@AfterThrowing(
        value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
        throwing = "targetMethodException"
)
public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP異常通知] "+methodName+"方法拋異常了,異常類型是:" + targetMethodException.getClass().getName());
}

4.切點表達式語法

1)切點表達式作用

AOP切點表達式(Pointcut Expression)是一種用于指定切點的語言,它可以通過定義匹配規則,來選擇需要被切入的目標對象。

2)切點表達式語法

3)切點表達式案例

1.查詢某包某類下,訪問修飾符是公有,返回值是int的全部方法
public int xx.xx.jj.*()
2.查詢某包下類中第一個參數是String的方法
* xx.xx.jj.*(String..)
3.查詢全部包下,無參數的方法!
* *..*.*()
4.查詢com包下,以int參數類型結尾的方法
* com..*.*(..int)
5.查詢指定包下,Service開頭類的私有返回值int的無參數方法
private int xx.xx.Service*.*()

5.重用/提取切點表達式

1)重用切點表達式的優點

之前編寫切點表達式時,發現, 許多增強方法的切點表達式相同!

出現了冗余,如果需要切換也不方便統一維護!

我們可以將切點提取,在增強上進行引用即可!

2)同一類內部引用

提取

// 切入點表達式重用
@Pointcut("execution(public int com.atguigu.aop.api.Calculator.add(int,int)))")
public void declarPointCut() {}

注意:提取切點注解使用@Pointcut(切點表達式) , 需要添加到一個無參數無返回值方法上即可!

引用

@Before(value = "declarPointCut()")
public void printLogBeforeCoreOperation(JoinPoint joinPoint) {

3)不同類中引用

不同類在引用切點,只需要添加類的全限定符+方法名即可!

@Before(value = "com.atguigu.spring.aop.aspect.LogAspect.declarPointCut()")
public Object roundAdvice(ProceedingJoinPoint joinPoint) {

4)切點統一管理

建議:將切點表達式統一存儲到一個類中進行集中管理和維護!

@Component
public class AtguiguPointCut {
    
    @Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
    public void atguiguGlobalPointCut(){}
    
    @Pointcut(value = "execution(public int *..Calculator.add(int,int))")
    public void atguiguSecondPointCut(){}
    
    @Pointcut(value = "execution(* *..*Service.*(..))")
    public void transactionPointCut(){}
}

6.環繞通知

環繞通知對應整個 try...catch...finally 結構,包括前面四種通知的所有功能。

// 使用@Around注解標明環繞通知方法
@Around(value = "com.atguigu.aop.aspect.AtguiguPointCut.transactionPointCut()")
public Object manageTransaction(
    
        // 通過在通知方法形參位置聲明ProceedingJoinPoint類型的形參,
        // Spring會將這個類型的對象傳給我們
        ProceedingJoinPoint joinPoint) {
    
    // 通過ProceedingJoinPoint對象獲取外界調用目標方法時傳入的實參數組
    Object[] args = joinPoint.getArgs();
    
    // 通過ProceedingJoinPoint對象獲取目標方法的簽名對象
    Signature signature = joinPoint.getSignature();
    
    // 通過簽名對象獲取目標方法的方法名
    String methodName = signature.getName();
    
    // 聲明變量用來存儲目標方法的返回值
    Object targetMethodReturnValue = null;
    
    try {
    
        // 在目標方法執行前:開啟事務(模擬)
        log.debug("[AOP 環繞通知] 開啟事務,方法名:" + methodName + ",參數列表:" + Arrays.asList(args));
    
        // 過ProceedingJoinPoint對象調用目標方法
        // 目標方法的返回值一定要返回給外界調用者
        targetMethodReturnValue = joinPoint.proceed(args);
    
        // 在目標方法成功返回后:提交事務(模擬)
        log.debug("[AOP 環繞通知] 提交事務,方法名:" + methodName + ",方法返回值:" + targetMethodReturnValue);
    
    }catch (Throwable e){
    
        // 在目標方法拋異常后:回滾事務(模擬)
        log.debug("[AOP 環繞通知] 回滾事務,方法名:" + methodName + ",異常:" + e.getClass().getName());
    
    }finally {
    
        // 在目標方法最終結束后:釋放數據庫連接
        log.debug("[AOP 環繞通知] 釋放數據庫連接,方法名:" + methodName);
    
    }
    
    return targetMethodReturnValue;
}

7.切面優先級設置

相同目標方法上同時存在多個切面時,切面的優先級控制切面的內外嵌套順序。

  • 優先級高的切面:外面
  • 優先級低的切面:里面

使用 @Order 注解可以控制切面的優先級:

  • @Order(較小的數):優先級高
  • @Order(較大的數):優先級低

小結

a. ?如果目標類有接口,選擇使用jdk動態代理

b. ?如果目標類沒有接口,選擇cglib動態代理

c. ?如果有接口,接口接值

d. ?如果沒有接口,類進行接值

原文鏈接:https://blog.csdn.net/weixin_69134926/article/details/136462079

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新