Spring 覆盤| AOP

Spring AOP 基礎 Java 動態代理實現,閱讀文章之前,你最好有以下基礎:

java動態代理

1、什麼是 AOP ?

AOP(Aspect Oriented Programming),即面向切面編程,它是 OOP(Object Oriented Programming,面向對象編程)的補充和完善。

在開發中,功能點通常分爲橫向關注點和核心關注點,核心關注點就是業務關注的點,大部分是要給用戶看的。而橫向關注點是用戶不關心,而我們程序又必須實現的,它的特點是橫向分佈於核心關注點各處,比如日誌功能,核心關注點:增刪改查都需要實現日誌功能。如果用 面向對象編程來實現的話,那增刪改查都需要寫一遍日誌代碼,這會造成非常多冗餘代碼,顯然是不合理的。而此時,AOP 應運而生。它統一定義了,何時、何處執行這些橫向功能點

2、AOP 相關術語

要理解 AOP 首先要認識以下相關術語,有這麼個場景,我需要給用戶模塊的增刪改查,實現日誌功能,我現在通過這個場景來解釋以上術語。

  • 連接點(joinpoint)

被攔截到的點,因爲 Spring 只支持方法類型的連接點,所以在 Spring 中連接點指的就是被攔截到的方法。場景中,連接點就是增刪改查方法本身。

  • 通知(advice)

所謂通知指的就是指攔截到連接點之後要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類。
1、前置通知(Before):在目標方法被調用之前調用通知功能;
2、後置通知(After):在目標方法完成之後調用通知,此時不會關
心方法的輸出是什麼;
3、返回通知(After-returning):在目標方法成功執行之後調用通
知;
4、異常通知(After-throwing):在目標方法拋出異常後調用通知;
5、環繞通知(Around):通知包裹了被通知的方法,在被通知的方
法調用之前和調用之後執行自定義的行爲。

  • 切點(pointcut)

對連接點進行攔截的定義,它會匹配通知所要織入的一個或多個連接點。它的格式是這樣的:

來自 Spring4 實戰

  • 切面(aspect)

類是對物體特徵的抽象,切面就是對橫切關注點的抽象,它定義了切點和通知。場景中,日誌功能就是這個抽象,它定義了你要對攔截方法做什麼?切面是通知和切點的結合。通知和切點共同定義了切面的全部內容——它是什麼,在何時和何處完成其功能。

  • 織入(weave)

將切面應用到目標對象並導致代理對象創建的過程

  • 引入(introduction)

在不修改代碼的前提下,引入可以在運行期爲類動態地添加一些方法或字段

3、註解實現 AOP

首先,定義一個加減乘除的接口,代碼如下:

public interface ArithmeticCalculator {

    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);

}

定義一個實現類,代碼如下:

@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        return result;
    }

}

定義切面,代碼如下:

/**
 * 1. 加入 jar 包
 * com.springsource.net.sf.cglib-2.2.0.jar
 * com.springsource.org.aopalliance-1.0.0.jar
 * com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
 * spring-aspects-4.0.0.RELEASE.jar
 *
 * 2. 在 Spring 的配置文件中加入 aop 的命名空間。
 *
 * 3. 基於註解的方式來使用 AOP
 * 3.1 在配置文件中配置自動掃描的包: <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>
 * 3.2 加入使 AspjectJ 註解起作用的配置: <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
 * 爲匹配的類自動生成動態代理對象.
 *
 * 4. 編寫切面類:
 * 4.1 一個一般的 Java 類
 * 4.2 在其中添加要額外實現的功能.
 *
 * 5. 配置切面
 * 5.1 切面必須是 IOC 中的 bean: 實際添加了 @Component 註解
 * 5.2 聲明是一個切面: 添加 @Aspect
 * 5.3 聲明通知: 即額外加入功能對應的方法.
 * 5.3.1 前置通知: @Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))")
 * @Before 表示在目標方法執行之前執行 @Before 標記的方法的方法體.
 * @Before 裏面的是切入點表達式:
 *
 * 6. 在通知中訪問連接細節: 可以在通知方法中添加 JoinPoint 類型的參數, 從中可以訪問到方法的簽名和方法的參數.
 *
 * 7. @After 表示後置通知: 在方法執行之後執行的代碼.
 */

//通過添加 @EnableAspectJAutoProxy 註解聲明一個 bean 是一個切面!
@Component
@Aspect
public class LoggingAspect {

    /**
     * 在方法正常開始前執行的代碼
     * @param joinPoint
     */
    @Before("execution(public int com.nasus.spring.aop.impl.*.*(int, int))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object [] args = joinPoint.getArgs();

        System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
    }

    /**
     * 在方法執行後執行的代碼,無論方法是否拋出異常
     * @param joinPoint
     */
    @After("execution(* com.nasus.spring.aop.impl.*.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends");
    }


    /**
     * 在方法正常結束後執行的代碼
     * 返回通知是可以訪問到方法的返回值的
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "execution(public int com.nasus.spring.aop.impl.*.*(int, int))",
    returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends with " + result);
    }

    /**
     * 在目標方法出現異常時,會執行的代碼
     * 可以訪問到異常對象,可以指定在出現特定異常時再執行通知代碼
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "execution(public int com.nasus.spring.aop.impl.*.*(int, int))",
    throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex){
        String methodNames = joinPoint.getSignature().getName();
        System.out.println("The method " + methodNames + " occurs exception: " + ex);
    }

    /**
     * 環繞通知需要攜帶 ProceedingJoinPoint 類型參數
     * 環繞通知類似於動態代理的全過程; ProceedingJoinPoint 類型的參數可以決定是否執行目標方法
     * 且環繞通知必須有返回值,返回值極爲目標方法的返回值
     * @param pjd
     * @return
     */
    @Around("execution(public int com.nasus.spring.aop.impl.*.*(int, int))")
    public Object aroundMethod(ProceedingJoinPoint pjd){

        Object result = null;
        String methodName = pjd.getSignature().getName();

        try {
            // 前置通知
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));

            // 執行目標方法
            result = pjd.proceed();

            // 返回通知
            System.out.println("The method " + methodName + " ends with " + result);
        }catch (Throwable e) {
            // 異常通知
            System.out.println("The method " + methodName + " occurs exception: " + e);
            throw new RuntimeException(e);
        }

        // 後置通知
        System.out.println("The method " + methodName + " ends");

        return result;
    }

}

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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 自動掃描的包 -->
    <context:component-scan base-package="com.nasus.spring.aop.impl"></context:component-scan>

    <!-- 使 AspectJ 的註解起作用 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<

測試方法:

public class Main {

    public static void main(String args[]){

        // 1、創建 Spring 的 IOC 容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext_aop.xml");

        // 2、從 IOC 容器中獲取 bean 實例
        ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);

        // 3、使用 bean
        arithmeticCalculator.add(3,6);
    }

}

測試結果:

The method add begins with [3, 6]
The method add begins with [3, 6]
The method add ends with 9
The method add ends
The method add ends
The method add ends with 9

4、xml 實現 AOP

關於 xml 的實現方式,網上發現一篇文章寫的不錯,此處,不再贅述,有興趣的參考以下鏈接:

https://www.cnblogs.com/hongwz/p/5764917.html

5、源碼地址

https://github.com/turoDog/review_spring

推薦閱讀:

1、java | 什麼是動態代理

2、SpringBoot | 啓動原理

3、SpringBoot | 自動配置原理

一個優秀的廢人

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章