面向切麵的Spring

来源:https://www.cnblogs.com/jeremyfa/archive/2019/06/24/11073359.html
-Advertisement-
Play Games

本篇內容說說Spring對切麵的支持,如何把普通類聲明為一個切麵,以及如何使用註解創建切麵,主要有以下幾點內容: 什麼是面向切麵編程 選擇連接點 使用註解創建切麵 在XML中聲明切麵 ...


本篇內容說說Spring對切麵的支持,如何把普通類聲明為一個切麵,以及如何使用註解創建切麵,主要有以下幾點內容:

  • 什麼是面向切麵編程
  • 選擇連接點
  • 使用註解創建切麵
  • 在XML中聲明切麵

什麼是面向切麵編程

切麵能幫助模塊化橫切關註點,橫切關註點可以被描述為影響應用多處的功能。如圖,直觀呈現橫切關註點的概念:

                                

 

 

上圖展現了一個被劃分為模塊的典型應用。每個模塊的核心功能都是為特定業務領域提供服務,但是這些模塊都需要類似的輔助功能,例如安全、事務管理、監控日誌等。

使用面向切麵編程時,在一個地方定義通用功能,可以通過聲明的方式定義這個功能要以何種方式在何處應用,而無需修改受影響的類。橫切關註點可以被模塊化為特殊的類,這些類被稱為切麵。這樣做有兩個好處:首先,每個關註點都集中於一個地方,而不是分散到多處代碼中;其次,服務模塊更簡潔,因為它們只包含主要關註點的代碼,而次要關註點的代碼被轉移到切麵中。

定義AOP術語

描述切麵的常用術語有通知(advice)、切點(pointcut)和連接點(join point),這幾個概念之間的關聯如圖所示:

                                    

 

通知(Advice)

 通知定義了切麵是什麼以及何時使用。Spring切麵可應用的通知有5種類型:

  • 前置通知(Before):在目標方法被調用之前調用通知;
  • 後置通知(After):在目標方法完成之後調用通知;
  • 返回通知(After-returning):在目標方法成功執行之後調用通知;
  • 異常通知(After-throwing):在目標方法拋出異常後調用通知;
  • 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為。

連接點(Join point)

連接點是在應用執行過程中能夠插入切麵的一個點,這個點可以是調用方法時、拋出異常時、甚至修改一個欄位時。切麵代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為。

切點(Pointcut)

切點的定義會匹配通知所要織入的一個或多個連接點。通常是使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。

切麵(Aspect)

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

引入(Introduction)

引入允許我們向現有的類添加新方法和屬性。

織入(Weaving)

織入是把切麵應用到目標對象並創建新的代理對象的過程。在目標對象的生命周期里有多個點可以進行織入:

  • 編譯期:切麵在目標類編譯時被織入,這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切麵的。
  • 類載入期:切麵在目標類載入到JVM時被織入。這種方式需要特殊的類載入器(ClassLoader),它可以在目標類被引入應用之前增強目標類的位元組碼。AspectJ 5的載入時織入(load-time weaving,LTW)就支持這種方式織入切麵。
  • 運行期:切麵在應用運行的某個時刻被織入。一般情況下,在織入切麵時,AOP容器會為目標對象動態地創建一個代理對象。Spring AOP就是以這種方式織入切麵的。

Spring對AOP的支持

不是所有的AOP框架都是相同的,它們在連接點模型上可能有強弱之分,有些允許在欄位修飾符級別應用通知,有些只支持方法調用相關的連接點。它們織入切麵的方式和時機也有所不同。總的來說,創建切點來定義切麵所織入的連接點是AOP框架的基本功能。

Spring提供了4種類型的AOP支持:

  • 基於代理的經典Spring AOP;
  • 純POJO切麵;
  • @AspectJ註解驅動的切麵;
  • 註入式AspectJ切麵

前三種都是Spring AOP實現的變體,Spring AOP構建在動態代理基礎之上,因此,Spring對AOP的支持局限方法攔截。

 Spring的經典AOP編程模型曾經非常棒,但現在Spring提供了更簡潔和乾凈的面向切麵編程方式。引入了簡單的聲明式AOP和基於註解的AOP,Spring經典的AOP看超來顯得非常笨重和過於複雜,使用ProxyFactory Bean會讓人感到繁瑣。所以這裡只寫簡單的聲明式AOP和基於註解的AOP。

藉助Spring的aop命名空間,可以將純POJO轉換為切麵。這些POJO只是提供了滿足切點條件時所要調用的方法,它需要XML配置。

Spring借鑒了AspectJ的切麵,提供註解驅動的AOP。它不需要XML配置,它雖然是基於代理的AOP,但編程模型與AspectJ註解切麵幾乎一致。

選擇連接點

Spring AOP的AspectJ切點,最重要的一點就是Spring僅支持AspectJ切點指示器(pointcut designator)的一個子集。Spring AOP支持的AspectJ切點指示器如下表:

AspectJ指示器

描述

arg()

限制連接點匹配參數為指定類型的執行方法

@args()

限制連接點匹配參數由指定註解標註的執行方法

execution()

用於匹配是連接點的執行方法

this()

限制連接點匹配AOP代理的bean引用為指定類型的類

target()

限制連接點匹配目標對象為指定類型的類

@target()

限制連接點匹配特定的執行對象,這些對象對應的類要具有指定類型的註解

within()

限制連接點匹配指定的類型

@within()

限制連接點匹配指定註解所標註的類型(當使用Spring AOP時,方法定義在由指定的註解所標註的類里)

@annotation

限定匹配帶有指定註解的連接點

只有execution指示器是實際執行匹配的,而其它的指示器都是用來限制匹配的。execution指示器是編寫切點定義時使用最多的指示器。

編寫切點

定義Performance介面作為切麵的切點:

package concert;

public interface Performance {

     public void perform();

}

現在展示一個切點表達式,這個表達式能夠設置當perform()方法執行時觸發通知的調用:

execution(* concert.Performance.perform(..))

使用execution()指示器選擇Performance的perform()方法。方法表達式以“*”號開始,表明不關心方法返回值的類型。還指定了全限定類名和方法名,對於方法參數列表,使用兩個點號(..)表明切點要選擇任意的perform()方法,無論該方法的入參是什麼。

假設需要配置的切點僅匹配concert包。在此場景下,可以使用within()指示器來限制匹配:

execution(* concert.Performance.perform(..)) && within(concert.*)

在切點中選擇bean

除了上面所列的指示器外,Spring還引入了一個新的bean()指示器,它允許在切點表達式中使用bean的ID來標識bean。bean()使用bean ID或bean名稱作為參數來限制切點只匹配的bean。

execution(* concert.Performance.perform(..)) and bean('dancer')

還可以使用非操作為除了特定ID以外的其他bean應用通知:

execution(* concert.Performance.perform(..)) and !bean('dancer')

 

使用註解創建切麵

Aspect提供了五個註解來定義通知:

  • @Before: 通知方法會在目標方法調用之前執行
  • @After: 通知方法會在目標方法返回或拋出異常後調用
  • @AfterReturning: 通知方法會在目標方法返回後調用
  • @Around: 通知方法會將目標方法封裝起來
  • @AfterThrowing: 通知方法會在目標方法拋出異常後調用

示例代碼

package asp;

import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RecordAspect {

    @Pointcut("execution(*  asp.P30Phone.call(..))")
    public void excute() {
    }

    @Before("excute()")
    public void before(JoinPoint joinPoint) {
        // 列印請求入參
        System.out.println("parameter:" + JSONObject.toJSONString(joinPoint.getArgs()));
    }

    @Around("excute()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        long endTime = System.currentTimeMillis();

        System.out.println("result:"+ result+", execute times:" + (endTime - beginTime) + " ms");

        return result;
    }

    @After("excute()")
    public void after(JoinPoint joinPoint) throws Throwable {
        System.out.println("after:" + joinPoint.toString());
    }
}
@Aspect定義一個切麵,@Pointcut 定義命名的切點

package asp;

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

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AspConfig {

}
@EnableAspectJAutoProxy 啟動AspectJ自動代理

package asp;

public interface HWPhone {
    String call(String user);
}

 

package asp;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("p30Phone")
@Qualifier("p30")
public class P30Phone implements HWPhone {
    @Override
    public String call(String user) {
        System.out.println("使用 P30 手機呼叫..."+user);
        return "p30";
    }
}

 

package asp;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspConfig.class);
        HWPhone hwPhone = (HWPhone) context.getBean("p30Phone");
        hwPhone.call("xiaoming");
    }
}

運行結果:

parameter:["xiaoming"]
使用 P30 手機呼叫...xiaoming
result:p30, execute times:51 ms
after:execution(String asp.HWPhone.call(String))

 

在XML中聲明切麵

在Spring的aop命名空間中, 提供了多個元素用來在XML中聲明切麵

 

AOP配置元素

用途

<aop:advisor>

定義AOP通知器

<aop:after>

定義AOP後置通知

<aop:after-returning>

定義AOP返回通知

<aop:after-throwing>

定義AOP異常通知

<aop:around>

定義AOP環繞通知

<aop:aspect>

定義一個切麵

<aop:aspectj-autoproxy>

啟用@AspectJ註解驅動的切麵

<aop:before>

定義一個AOP前置通知

<aop:config>

頂層的AOP配置元素。大多數的<aop:*>元素必須包含在<aop:config>元素內

<aop:declare-parents>

以透明的方式為被通知的對象引入額外的介面

<aop:pointcut>

定義一個切點

 

示例代碼
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <bean id="p30Phone" class="asp.P30Phone"/>
    <bean id="recordAspect" class="asp.RecordAspect" />

    <aop:config>
        <aop:aspect ref="recordAspect">
            <aop:pointcut id="execute" expression="execution(*  asp.P30Phone.call(..))"/>
            <aop:before pointcut-ref="execute" method="before" />
            <aop:after pointcut-ref="execute" method="after" />
            <aop:around pointcut-ref="execute" method="around" />
        </aop:aspect>
    </aop:config>

</beans>

 

package asp;

import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class RecordAspect {

    public void before(JoinPoint joinPoint) {
        // 列印請求入參
        System.out.println("parameter:" + JSONObject.toJSONString(joinPoint.getArgs()));
    }

    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        long endTime = System.currentTimeMillis();

        System.out.println("result:"+ result+", execute times:" + (endTime - beginTime) + " ms");

        return result;
    }

    public void after(JoinPoint joinPoint) throws Throwable {
        System.out.println("after:" + joinPoint.toString());
    }
}

 

package asp;

public interface HWPhone {
    String call(String user);
}

 

package asp;

public class P30Phone implements HWPhone {
    @Override
    public String call(String user) {
        System.out.println("使用 P30 手機呼叫..."+user);
        return "p30";
    }
}

 

package asp;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("asp/application.xml");
        HWPhone hwPhone = (HWPhone) context.getBean("p30Phone");
        hwPhone.call("xiaoming");
    }
}

 

運行結果:

parameter:["xiaoming"]
使用 P30 手機呼叫...xiaoming
result:p30, execute times:0 ms
after:execution(String asp.HWPhone.call(String))

 

參考《Spring實戰(第4版)》

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 課程作業: ...
  • 一、SQLAlchemy簡介 SQLAlchemy是Python SQL工具包和對象關係映射器,是python中最著名的ORM(Object Relationship Mapping)框架,它簡化了應用程式開發人員在原生SQL上的操作,使開發人員將主要精力都放在程式邏輯上,從而提高開發效率。它提供了 ...
  • 一、變數作用域 當程式定義一個變數時,這個變數是有它的作用範圍的,變數的作用範圍稱為變數的作用域。根據變數的位置,分為兩種: 局部變數:局部變數就是在函數中定義的變數,包括參數,都是局部變數,局部離開函數後,將不能被訪問。 全局變數:不在函數內定義、全局範圍內定義的變數,都是全局變數,全局變數可以在 ...
  • 前面介紹了怎樣通過Socket在客戶端與服務端之間傳輸文本,當然Socket也支持在客戶端與服務端之間傳輸文件,因為文件本身就是通過I/O流實現讀寫操作的,所以在套接字的輸入輸出流中傳輸文件真是再合適不過了。只是套接字屬於長連接,倘若Socket一直不關閉,連接將總是處於就緒狀態,也就無法判斷文件數 ...
  • 封裝和@property 一、複習 1、介面類和抽象類 python中沒有介面類,有抽象類,abc模塊中的metaclass=ABCMeta,@abstructmethod,本質是做代碼規範用的,希望在子類中實現和父類方法名完全一樣的方法 在Java的角度上是有區別的: Java本來支持單繼承,所以 ...
  • 一、模板常用語法 1.1 變數 1. 符號:{{ }} 表示變數,在模板渲染的時候替換成值 使用方式:{{ 變數名 }}:變數名由字母數字和下劃線組成 點(.)在模板語言中有特殊的含義,用來獲取對象的相應屬性值 註意:當模板系統遇到一個(.)時,會按照如下的順序去查詢: 在字典中查詢 屬性或者方法 ...
  • 6.9 time 模塊 1、時間戳(以秒計算) 2、格式化的字元串 3、struct_time()對象 4、 mktime( t ) : 將一個struct_time轉化為時間戳 5、time.strptime() 6、time.asctime() 7、time.ctime() 6.10 datet ...
  • 一、希爾排序的介紹 希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。 希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的記錄越來越多,當增量減至1時,整個文件恰被分 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...