基於註解式的SpringAOP源碼解析-1-SpringAOP代碼示例

基於註解式的SpringAOP源碼解析前言

AOP通俗地來講就是指在程序運行期間動態的將某段代碼切入到指定方法的指定位置進行運行的編程方式,
這次我將會來對基於註解式的SpringAOP做源碼解析

基於註解式的SpringAOP使用代碼示例

代碼工程結構圖

代碼工程結構圖

代碼處理步驟

1、pom.xml導入aop模塊

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>review-java</artifactId>
        <groupId>review</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>review-spring</artifactId>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

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

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2、定義一個業務邏輯類 MathCalculator.java

本次aop要實現的目標是在方法div運行的時候將日誌進行打印(在方法之前、方法運行結束、方法出現異常等運行位置進行日誌打印)

package aop.annotation.service;

public class MathCalculator {
    /** * 除法 * * @param i * @param j * @return */
    public int div(int i, int j) {
        System.out.println("MathCalculator.div方法被調用");
        return i / j;
    }
}

3、定義一個日誌切面類LogAspects.java

切面類裏面的方法需要動態感知MathCalculator.div運行到哪裏,然後執行相應的通知方法
給切面類的目標方法標註何時何地運行(即以下通知註解)
LogAspects 裏面有通知方法:
1)前置通知:(@Before) logStart:在目標方法div運行之前
2)後置通知:(@Aftre) logEnd:在目標方法div運行結束之後 (無論方法正常結束還是異常結束)
3)返回通知:(@AfterReturning) logReturn:在目標方法div正常返回之後運行
4)異常通知:(@AfterThrowing) logException:在目標方法div出現異常之後運行
5)環繞通知:(@Around) 動態代理,手動推進目標方法運行(joinPoint.proceed())

package aop.annotation.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;

/** * @Aspect 此註解告訴spring這是切面 */
@Aspect
public class LogAspects {

    /** * 抽取公共的切入點表達式 * 1、如果在本類引用 直接在註解加方法名 如@Before(commonPointCut()) * 2、如果不是在本類引用 (比如其他的切面要引用這個切入點) 則要用全類名 */
    @Pointcut("execution(public int aop.annotation.service.MathCalculator.div(int, int))")
    public void commonPointCut() {

    }
    
    //@Before在目標方法之前切入;切入點表達式(指定在哪個方法切入)
    //@Before("public int aop.annotation.service.MathCalculator.div(int, int)")
    /** * @param joinPoint 可以通過此參數獲取方法相關信息 如方法名和參數信息,此參數必須放在第一個參數 */
    @Before("commonPointCut()")
    public void logStart(JoinPoint joinPoint) {
        System.out.println("" + joinPoint.getSignature().getName() + "除法運行,@Before,參數列表是:{" + Arrays.asList(joinPoint.getArgs()) + "}");
    }


    @After("commonPointCut()")
    public void logEnd(JoinPoint joinPoint) {
        System.out.println("除法結束,@After");
    }

    /** * 用returning來接收返回值 * * @param result */
    @AfterReturning(value = "commonPointCut()", returning = "result")
    public void logReturn(Object result) {
        System.out.println("除法正常返回,@AfterReturning 運行結果:{" + result + "}");
    }

    /** * 用throwing來接收異常 * * @param joinPoint 此參數必須放在第一位 * @param exception */
    @AfterThrowing(value = "commonPointCut()", throwing = "exception")
    public void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("" + joinPoint.getSignature().getName() + "除法異常,@AfterException 異常信息:{" + exception.getStackTrace() + "}");
    }
}

4、將切面類和業務邏輯類加入到容器中 MainConfigOfAOP.java

告訴spring哪個是切面類,(在切面類LogAspects加一個註解@Aspect)
在配置類中加 @EnableAspectJAutoProxy 開啓基於註解的aop模式

package aop.annotation.config;

import aop.annotation.aop.LogAspects;
import aop.annotation.service.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;


/** * 三步: * 1)、將業務邏輯組件和切面類都加入到容器中;告訴spring哪個是切面類(@Aspect) * 2)、在切面類的每一個通知方法上標註通知註解,告訴spring何時何地運行(切入點表達式) * 3)、開啓基於註解的aop模式 @EnableAspectJAutoProxy */

@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {


    @Bean
    public MathCalculator calculator() {
        return new MathCalculator();
    }
    @Bean
    public LogAspects logAspects() {
        return new LogAspects();
    }
}

5、編寫單元測試類進行測試 IOCTest_IOC.java

package aop.annotation.test;

import aop.annotation.config.MainConfigOfAOP;
import aop.annotation.service.MathCalculator;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IOCTest_AOP {

    /** * AOP單元測試 */
    @Test
    public void testAop() {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
        MathCalculator mathCalculator = annotationConfigApplicationContext.getBean(MathCalculator.class);
        mathCalculator.div(1, 0);
        annotationConfigApplicationContext.close();
    }
}

6、運行結果

本次測試1除以0的異常情況:可見在div運行之前,運行之後,以及出現異常時都打印出相關的信息。
其他情況,大家可以自行測試
aop的效果就是這樣,在div運行前後的各個位置進行了處理,把日誌的代碼與業務邏輯的代碼進行解耦。
運行結果

下一篇預告

下一篇將開始進行源碼解析,將首先通過源碼解析註解@EnableAspectJAutoProxy的作用