1. 程式人生 > 程式設計 >Spring 覆盤 | AOP

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,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 的實現方式,網上發現一篇文章寫的不錯,此處,不再贅述,有興趣的參考以下連結:

www.cnblogs.com/hongwz/p/57…

5、原始碼地址

github.com/turoDog/rev…

推薦閱讀:

1、java | 什麼是動態代理

2、SpringBoot | 啟動原理

3、SpringBoot | 自動配置原理

一個優秀的廢人