SpringBoot基礎篇AOP之基本使用姿式小結

原文:190301-SpringBoot基礎篇AOP之基本使用姿式小結java

通常來說,談到Spring的特性,繞不過去的就是DI(依賴注入)和AOP(切面),在將bean的系列中,說了DI的多種使用姿式;接下來看一下AOP的玩法git

<!-- more -->github

I. 背景知識

在實際使用以前有必要了解一下什麼是AOP,以及AOP的幾個基本概念正則表達式

1. advice

  • before: 在方法執行以前被調用
  • after: 在方法執行以後調用
  • after returning: 方法執行成功以後
  • after throwing: 方法拋出異常以後
  • around: 環繞,本身在內部決定方法的執行時機,所以能夠在以前以後作一些業務邏輯

2. join point

鏈接點,好比方法調用,方法執行,字段設置/獲取、異常處理執行、類初始化、甚至是 for 循環中的某個點spring

但 Spring AOP 目前僅支持方法執行 (method execution)安全

簡單來講,Spring AOP中,PointCut就是那個被攔截的方法框架

3. pointcut

切點,用來描述知足什麼規則的方法會被攔截dom

  • 正則表達式 : @Before("execution(public * com.git.hui.demo.base.bean.*.*(..))")
  • 註解攔截方式 :@Around("@annotation(parameterCheck)")

4. aspect

切面是切點和通知的結合。通知和切點共同定義了關於切面的所有內容,它是何時,在什麼時候和何處完成功能maven

5. introduction

引入容許咱們向現有的類添加新的方法或者屬性spring-boot

6. weaving

組裝方面來建立一個被通知對象。這能夠在編譯時完成(例如使用AspectJ編譯器),也能夠在運行時完成。Spring和其餘純Java AOP框架同樣,在運行時完成織入。

簡單來說就是生成一個代理類,在調用被攔截的方法時,實際上執行的是代理類,這個代理類內部執行切面邏輯

II. 使用說明

1. 基本配置

首先是基本環境的搭建, 先貼上必要的xml配置, 使用aop須要引入包: spring-boot-starter-aop

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    <java.version>1.8</java.version>
</properties>


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

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

2. 代碼準備

首先建立一個被攔截的bean: com.git.hui.boot.aop.demo.DemoBean,以下

@Component
public class DemoBean {

    /**
     * 返回隨機的字符串
     *
     * @param time
     * @return
     */
    public String randUUID(long time) {
        try {
            System.out.println("in randUUID before process!");
            return UUID.randomUUID() + "|" + time;
        } finally {
            System.out.println("in randUUID finally!");
        }
    }
}

接着在啓動類中,執行

@SpringBootApplication
public class Application {
    
    public Application(DemoBean demoBean) {
        String ans = demoBean.randUUID(System.currentTimeMillis());
        System.out.println("----- ans: " + ans + "---------");
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

3. AOP使用

在實際使用以前,須要建立一個切面,用@Aspect聲明,其次切面也須要做爲bean託付給Spring容器管理

@Aspect
@Component
public class AnoAspcet {
}

a. before

在方法調用以前,須要執行一些操做,這個時候可使用 @Before 註解來聲明before advice

一種可以使用姿式以下,咱們的切點直接在註解中進行定義,使用正則表達式的方式

@Before("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void doBefore(JoinPoint joinPoint) {
    System.out.println("do in Aspect before method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}

b. after

在方法調用完畢以後,再執行一些操做,這個時候after就能夠派上用場,爲了考慮切點的通用性,咱們能夠考慮聲明一個切點,使用@Pointcut註解

@Pointcut("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void point() {
}

使用pointcut的方式也比較簡單,以下

@After("point()")
public void doAfter(JoinPoint joinPoint) {
    System.out.println("do in Aspect after method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}

c. after returning

在正常返回結果以後,再次執行,這個也挺有意思的,一般使用這個advice時,通常但願獲取返回結果,那麼應該怎麼處理呢?

  • org.aspectj.lang.annotation.AfterReturning#returning 指定返回結果對應參數name
  • 返回結果做爲參數傳入,要求類型一致,不然不生效
/**
 * 執行完畢以後,經過 args指定參數;經過 returning 指定返回的結果,要求返回值類型匹配
 *
 * @param time
 * @param result
 */
@AfterReturning(value = "point() && args(time)", returning = "result")
public void doAfterReturning(long time, String result) {
    System.out.println("do in Aspect after method return! args: " + time + " ans: " + result);
}

d. around

這個也比較常見,在方法執行先後幹一些事情,好比常見的耗時統計,日誌打印,安全控制等,不少都是基於around advice實現的

使用這個advice須要注意的是傳入參數類型爲 ProceedingJoinPoint,須要在方法內部顯示執行org.aspectj.lang.ProceedingJoinPoint#proceed()來表示調用方法

@Around("point()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("do in Aspect around ------ before");
    Object ans = joinPoint.proceed();
    System.out.println("do in Aspect around ------- over! ans: " + ans);
    return ans;
}

e. 輸出

執行以後輸出以下

do in Aspect around ------ before
do in Aspect before method called! args: [1551433188205]
in randUUID before process!
in randUUID finally!
do in Aspect around ------- over! ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205
do in Aspect after method called! args: [1551433188205]
do in Aspect after method return! args: 1551433188205 ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205
----- ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205---------

從輸出結果上,能夠看到每一個advice的使用範圍,固然也帶來了一些疑問

  • 能夠存在多個同類型的advice,攔截同一個目標嗎?(如兩個around都攔截methodA方法,那麼methodA方法被調用時,兩個around advice是否都會執行)
  • 多個advice之間的優先級怎麼定義?
  • aop攔截的目標方法有沒有限制(對非public的方法能夠攔截麼?)
  • 被攔截的方法中存在相互調用的時候,會怎樣?(如methodA,methodB均可以被攔截,且methodA中調用了methodB,那麼在執行methodA時,methodB的各類advice是否會被觸發?)
  • 基於註解的aop方式能夠怎樣用

以上這些問題留在下一篇進行介紹

III. 其餘

0. 項目

1. 一灰灰Blog

一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

2. 聲明

盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

一灰灰blog

QrCode

知識星球

goals

相關文章
相關標籤/搜索