1. 程式人生 > >Spring入門(十):Spring AOP使用講解

Spring入門(十):Spring AOP使用講解

1. 什麼是AOP?

AOP是Aspect Oriented Programming的縮寫,意思是:面向切面程式設計,它是通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。

可以認為AOP是對OOP(Object Oriented Programming 面向物件程式設計)的補充,主要使用在日誌記錄,效能統計,安全控制等場景,使用AOP可以使得業務邏輯各部分之間的耦合度降低,只專注於各自的業務邏輯實現,從而提高程式的可讀性及維護性。

比如,我們需要記錄專案中所有對外介面的入參和出參,以便出現問題時定位原因,在每一個對外介面的程式碼中新增程式碼記錄入參和出參當然也可以達到目的,但是這種硬編碼的方式非常不友好,也不夠靈活,而且記錄日誌本身和介面要實現的核心功能沒有任何關係。

此時,我們可以將記錄日誌的功能定義到1個切面中,然後通過宣告的方式定義要在何時何地使用這個切面,而不用修改任何1個外部介面。

在講解具體的實現方式之前,我們先了解幾個AOP中的術語。

1.1 通知(Advice)

在AOP術語中,切面要完成的工作被稱為通知,通知定義了切面是什麼以及何時使用。

Spring切面有5種類型的通知,分別是:

  • 前置通知(Before):在目標方法被呼叫之前呼叫通知功能
  • 後置通知(After):在目標方法完成之後呼叫通知,此時不關心方法的輸出結果是什麼
  • 返回通知(After-returning):在目標方法成功執行之後呼叫通知
  • 異常通知(After-throwing):在目標方法丟擲異常後呼叫通知
  • 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為

1.2 連線點(Join point)

連線點是在應用執行過程中能夠插入切面的一個點,這個點可以是呼叫方法時、丟擲異常時、修改某個欄位時。

1.3 切點(Pointcut)

切點是為了縮小切面所通知的連線點的範圍,即切面在何處執行。我們通常使用明確的類和方法名稱,或者利用正則表示式定義所匹配的類和方法名稱來指定切點。

1.4 切面(Aspect)

切面是通知和切點的結合。通知和切點共同定義了切面的全部內容:它是什麼,在何時和何處完成其功能。

1.5 引入(Introduction)

引入允許我們在不修改現有類的基礎上,向現有類新增新方法或屬性。

1.6 織入(Weaving)

織入是把切面應用到目標物件並建立新的代理物件的過程。

切面在指定的連線點被織入到目標物件中,在目標物件的生命週期裡,有以下幾個點可以進行織入:

  • 編譯期:切面在目標類編譯時被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
  • 類載入期:切面在目標類載入到JVM時被織入。這種方式需要特殊的類載入器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的位元組碼。
  • 執行期:切面在應用執行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標物件動態地建立一個代理物件。Spring AOP就是以這種方式織入切面的。

2. Spring 對AOP的支援

2.1 動態代理

Spring AOP構建在動態代理之上,也就是說,Spring執行時會為目標物件動態建立代理物件。

代理類封裝了目標類,並攔截被通知方法的呼叫,再把呼叫轉發給真正的目標bean。

當代理類攔截到方法呼叫時,在呼叫目標bean方法之前,會執行切面邏輯。

2.2 織入切面時機

通過在代理類中包裹切面,Spring在執行期把切面織入到Spring 管理的bean中,也就是說,直到應用需要被代理的bean時,Spring才會建立代理物件。

因為Spring執行時才建立代理物件,所以我們不需要特殊的編譯器來織入Spring AOP切面。

2.3 連線點限制

Spring只支援方法級別的連線點,如果需要欄位級別或者構造器級別的連線點,可以利用AspectJ來補充Spring AOP的功能。

3. Spring AOP使用

假設我們有個現場表演的介面Performance和它的實現類SleepNoMore:

package chapter04.concert;

/**
 * 現場表演,如舞臺劇,電影,音樂會
 */
public interface Performance {
    void perform();
}
package chapter04.concert;

import org.springframework.stereotype.Component;

/**
 * 戲劇:《不眠之夜Sleep No More》
 */
@Component
public class SleepNoMore implements Performance {
    @Override
    public void perform() {
        System.out.println("戲劇《不眠之夜Sleep No More》");
    }
}

既然是演出,就需要觀眾,假設我們的需求是:在看演出之前,觀眾先入座並將手機調整至靜音,在觀看演出之後觀眾鼓掌,如果演出失敗觀眾退票,我們當然可以把這些邏輯寫在上面的perform()方法中,但不推薦這麼做,因為這些邏輯理論上和演出的核心無關,就算觀眾不將手機調整至靜音或者看完演出不鼓掌,都不影響演出的進行。

針對這個需求,我們可以使用AOP來實現。

3.1 定義切面

首先,定義一個觀眾的切面如下:

package chapter04.concert;

import org.aspectj.lang.annotation.Aspect;

/**
 * 觀眾
 * 使用@Aspect註解定義為切面
 */
@Aspect
public class Audience {
}

注意事項:@Aspect註解表明Audience類是一個切面。

3.2 定義前置通知

在Audience切面中定義前置通知如下所示:

/**
 * 表演之前,觀眾就座
 */
@Before("execution(* chapter04.concert.Performance.perform(..))")
public void takeSeats() {
    System.out.println("Taking seats");
}

/**
 * 表演之前,將手機調至靜音
 */
@Before("execution(* chapter04.concert.Performance.perform(..))")
public void silenceCellPhones() {
    System.out.println("Silencing cell phones");
}

這裡的重點程式碼是@Before("execution(* chapter04.concert.Performance.perform(..))"),它定義了1個前置通知,其中execution(* chapter04.concert.Performance.perform(..))被稱為AspectJ切點表示式,每一部分的講解如下:

  • @Before:該註解用來定義前置通知,通知方法會在目標方法呼叫之前執行
  • execution:在方法執行時觸發
  • *:表明我們不關心方法返回值的型別,即可以是任意型別
  • chapter04.concert.Performance.perform:使用全限定類名和方法名指定要新增前置通知的方法
  • (..):方法的引數列表使用(..),表明我們不關心方法的入參是什麼,即可以是任意型別

3.3 定義後置通知

在Audience切面中定義後置通知如下所示:

/**
 * 表演結束,不管表演成功或者失敗
 */
@After("execution(* chapter04.concert.Performance.perform(..))")
public void finish() {
    System.out.println("perform finish");
}

注意事項:@After註解用來定義後置通知,通知方法會在目標方法返回或者丟擲異常後呼叫

3.4 定義返回通知

在Audience切面中定義返回通知如下所示:

/**
 * 表演之後,鼓掌
 */
@AfterReturning("execution(* chapter04.concert.Performance.perform(..))")
public void applause() {
    System.out.println("CLAP CLAP CLAP!!!");
}

注意事項:@AfterReturning註解用來定義返回通知,通知方法會在目標方法返回後呼叫

3.5 定義異常通知

在Audience切面中定義異常通知如下所示:

/**
 * 表演失敗之後,觀眾要求退款
 */
@AfterThrowing("execution(* chapter04.concert.Performance.perform(..))")
public void demandRefund() {
    System.out.println("Demanding a refund");
}

注意事項:@AfterThrowing註解用來定義異常通知,通知方法會在目標方法丟擲異常後呼叫

3.6 定義可複用的切點表示式

細心的你可能會發現,我們上面定義的5個切點中,切點表示式都是一樣的,這顯然是不好的,好在我們可以使用@Pointcut註解來定義可重複使用的切點表示式:

/**
 * 可複用的切點
 */
@Pointcut("execution(* chapter04.concert.Performance.perform(..))")
public void perform() {
}

然後之前定義的5個切點都可以引用這個切點表示式:

/**
 * 表演之前,觀眾就座
 */
@Before("perform()")
public void takeSeats() {
    System.out.println("Taking seats");
}

/**
 * 表演之前,將手機調至靜音
 */
@Before("perform()")
public void silenceCellPhones() {
    System.out.println("Silencing cell phones");
}

/**
 * 表演結束,不管表演成功或者失敗
 */
@After("perform()")
public void finish() {
    System.out.println("perform finish");
}

/**
 * 表演之後,鼓掌
 */
@AfterReturning("perform()")
public void applause() {
    System.out.println("CLAP CLAP CLAP!!!");
}

/**
 * 表演失敗之後,觀眾要求退款
 */
@AfterThrowing("perform()")
public void demandRefund() {
    System.out.println("Demanding a refund");
}

3.7 單元測試

新建配置類ConcertConfig如下所示:

package chapter04.concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
    @Bean
    public Audience audience() {
        return new Audience();
    }
}

注意事項:和以往不同的是,我們使用了@EnableAspectJAutoProxy註解,該註解用來啟用自動代理功能。

新建Main類,在其main()方法中新增如下測試程式碼:

package chapter04.concert;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConcertConfig.class);

        Performance performance = context.getBean(Performance.class);
        performance.perform();

        context.close();
    }
}

執行程式碼,輸出結果如下所示:

Silencing cell phones

Taking seats

戲劇《不眠之夜Sleep No More》

perform finish

CLAP CLAP CLAP!!!

稍微修改下SleepNoMore類的perform()方法,讓它丟擲一個異常:

@Override
public void perform() {
    int number = 3 / 0;
    System.out.println("戲劇《不眠之夜Sleep No More》");
}

再次執行程式碼,輸出結果如下所示:

Silencing cell phones

Taking seats

perform finish

Demanding a refund

Exception in thread "main" java.lang.ArithmeticException: / by zero

由此也可以說明,不管目標方法是否執行成功,@After註解都會執行,但@AfterReturning註解只會在目標方法執行成功時執行。

值得注意的是,使用@Aspect註解的切面類必須是一個bean(不管以何種方式宣告),否則切面不會生效,因為AspectJ自動代理只會為使用@Aspect註解的bean建立代理類。

也就是說,如果我們將ConcertConfig配置類中的以下程式碼刪除或者註釋掉:

@Bean
public Audience audience() {
    return new Audience();
}

執行結果將變為:

戲劇《不眠之夜Sleep No More》

3.8 建立環繞通知

我們可以使用@Around註解建立環繞通知,該註解能夠讓你在呼叫目標方法前後,自定義自己的邏輯。

因此,我們之前定義的5個切點,現在可以定義在一個切點中,為不影響之前的切面,我們新建切面AroundAudience,如下所示:

package chapter04.concert;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AroundAudience {
    /**
     * 可重用的切點
     */
    @Pointcut("execution(* chapter04.concert.Performance.perform(..))")
    public void perform() {
    }

    @Around("perform()")
    public void watchPerform(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("Taking seats");
            System.out.println("Silencing cell phones");

            joinPoint.proceed();

            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
        } finally {
            System.out.println("perform finish");
        }
    }
}

這裡要注意的是,該方法有個ProceedingJoinPoint型別的引數,在方法中可以通過呼叫它的proceed()方法來呼叫目標方法。

然後修改下ConcertConfig類的程式碼:

package chapter04.concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
    /*@Bean
    public Audience audience() {
        return new Audience();
    }*/

    @Bean
    public AroundAudience aroundAudience() {
        return new AroundAudience();
    }
}

執行結果如下所示:

Taking seats

Silencing cell phones

戲劇《不眠之夜Sleep No More》

CLAP CLAP CLAP!!!

perform finish

4. 原始碼及參考

原始碼地址:https://github.com/zwwhnly/spring-action.git,歡迎下載。

Craig Walls 《Spring實戰(第4版)》

AOP(面向切面程式設計)_百度百科

5. 最後

打個小廣告,歡迎掃碼關注微信公眾號:「申城異鄉人」,定期分享Java技術乾貨,讓我們一起進步。

相關推薦

Spring入門()Spring AOP使用講解

1. 什麼是AOP? AOP是Aspect Oriented Programming的縮寫,意思是:面向切面程式設計,它是通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。 可以認為AOP是對OOP(Object Oriented Programming 面向物件程式設計)的補充,主要使用在日誌

Spring入門(五)Spring Bean Scope講解

1. 前情回顧 Spring入門(一):建立Spring專案 Spring入門(二):自動化裝配bean Spring入門(三):通過JavaConfig裝配bean Spring入門(四):使用Maven管理Spring專案 2. 什麼是Bean的Scope? Scope描述的是Spring容器是如何新

Spring入門(七)Spring Profile使用講解

1. 使用場景 在日常的開發工作中,我們經常需要將程式部署到不同的環境,比如Dev開發環境,QA測試環境,Prod生產環境,這些環境下的一些配置肯定是不一樣的,比如資料庫配置,Redis配置,RabbitMQ配置。 如果每次切換髮布環境,都需要修改配置重新構建的話,那對程式設計師來說將是噩夢,針對這種場景,S

Spring入門(十三)Spring MVC常用註解講解

在使用Spring MVC開發Web應用程式時,控制器Controller的開發非常重要,雖然說檢視(JSP或者是Thymeleaf)也很重要,因為它才是直接呈現給使用者的,不過由於現在前端越來越重要,很多公司都開始採用前後端分離的開發模式,所以我們暫時可以將精力放在開發控制器上。 使用Spring MVC開

Spring入門(一)Spring注入

概念 Spring注入是指在啟動Spring容器載入bean配置的時候,完成對變數的賦值行為 常用的兩種注入方式 設值注入 構造注入 設值注入 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns

Spring入門(一)Spring AOP使用進階

在上篇部落格中,我們瞭解了什麼是AOP以及在Spring中如何使用AOP,本篇部落格繼續深入講解下AOP的高階用法。 1. 宣告帶引數的切點 假設我們有一個介面CompactDisc和它的實現類BlankDisc: package chapter04.soundsystem; /** * 光碟 */ p

Spring入門(二)Spring MVC使用講解

1. Spring MVC介紹 提到MVC,參與過Web應用程式開發的同學都很熟悉,它是展現層(也可以理解成直接展現給使用者的那一層)開發的一種架構模式,M全稱是Model,指的是資料模型,V全稱是View,指的是檢視頁面,如JSP、Thymeleaf等,C全稱是Controller,指的是控制器,用來處理使

Spring入門(四)Spring MVC控制器的2種測試方法

作為一名研發人員,不管你願不願意對自己的程式碼進行測試,都得承認測試對於研發質量保證的重要性,這也就是為什麼每個公司的技術部都需要質量控制部的原因,因為越早的發現程式碼的bug,成本越低,比如說,Dev環境發現bug的成本要低於QA環境,QA環境發現bug的成本要低於Prod環境,Prod環境發現bug的成本

Spring入門(五)使用Spring JDBC操作資料庫

在本系列的之前部落格中,我們從沒有講解過操作資料庫的方法,但是在實際的工作中,幾乎所有的系統都離不開資料的持久化,所以掌握操作資料庫的使用方法就非常重要。 在Spring中,操作資料庫有很多種方法,我們可以使用JDBC、Hibernate、MyBatis或者其他的資料持久化框架,本篇部落格的重點是講解下在Sp

《Java從入門到放棄》入門spring中IOC的註入姿勢

java ioc spring IOC到底是個什麽東東呢?控制反轉(Inversion of Control,英文縮寫為IoC),其實就是這個東東。你隨便百度一下就會得到比較書面的解釋:通過引入實現了IoC模式的IoC容器,即可由IoC容器來管理對象的生命周期、依賴關系等,從而使得應用程序的配置和

Spring技術內幕Spring AOP的實現原理(三)

dede ide configure ida mini == src min dem 生成SingleTon代理對象在getSingleTonInstance方法中完畢,這種方法時ProxyFactoryBean生成AopProxy對象的入口。代理對象會

Spring學習(六)----- Spring AOP實例(Pointcut(切點),Advisor)

dao new ide on() inter exc def row 再次 在上一個Spring AOP通知的例子,一個類的整個方法被自動攔截。但在大多數情況下,可能只需要一種方式來攔截一個或兩個方法,這就是為什麽引入‘切入點‘的原因。它允許你通過它的方法名來攔截方法。另

《01.Spring Boot連載Spring Boot入門介紹》

spring boot maven 1 Spring Boot的概述Spring Boot是開發者和Spring 本身框架的中間層,幫助開發者統籌管理應用的配置,提供基於實際開發中常見配置的默認處理(即習慣優於配置),簡化應用的開發,簡化應用的運維;總的來說,其目的Spring Boot就是為了對Ja

Spring入門(三)— AOP註解、jdbc模板、事務

list() 規範 行數 get attribute 樂觀鎖 過濾 callback 賬號 一、AOP註解開發 導入jar包 aop聯盟包、 aspectJ實現包 、 spring-aop-xxx.jar 、 spring-aspect-xxx.jar 導入約束 a

spring入門框架整體簡介

mil object web開發 spa tor 對象 j2ee 就是 cor 1:spring的基本框架主要包含六大模塊:DAO、ORM、AOP、JEE、WEB、CORE   DAO:(Data Access Object) 數據訪問對象,是一個面向對象的數據庫接口。  

Spring第一天Spring的概述、SpringIOC入門(XML)、Spring的Bean管理、Spring屬性注入

以前也學習過Spring框架,不過好久沒用,當時學完也沒做什麼總結,都忘的差不多了,今天再從頭開始學習一遍。無論是SSH還是SSM都離不開Spring,所以Spring還是很重要的,對於一個想要從事JavaEE開發的人,一定要好好學習Spring框架。Spring的學習計劃如下: 第一

Spring第二天Spring的IOC的註解方式、SpringAOP開發(XML)

   Spring第二天繼續,今天要學習的是Spring的IOC的註解方式和Spring的AOP開發(XML) 目錄 1.Spring的IOC註解方式開發 1.1Spring的IOC註解開發入門 1.1.1建立Web專案引入Jar包 1.1.2引入Spri

Spring Boot ()打包部署

springboot 打包與部署 一、jar 包 進入命令列模式 ,不管linux 還是windows 都是一樣的 1、編譯 進入專案目錄,使用如下命令: //命令打包(-Dmaven.test.skip=true 跳過測試)

Spring Cloud 3Spring Boot快速入門

簡介 在您第1次接觸和學習Spring框架的時候,是否因為其繁雜的配置而退卻了?在你第n次使用Spring框架的時候,是否覺得一堆反覆黏貼的配置有一些厭煩?那麼您就不妨來試試使用Spring Boot來讓你更易上手,更簡單快捷地構建Spring應用! Spring B

Spring入門(二)自動化裝配bean

pep 自動化 tdi req ngs mcr 完成 injection 實現類 Spring從兩個角度來實現自動化裝配: 組件掃描(component scanning):Spring會自動發現應用上下文中需要創建的bean。 自動裝配(autowiring):Spri