最全的Spring AOP

来源:https://www.cnblogs.com/naihuangbao/archive/2018/12/24/10169818.html
-Advertisement-
Play Games

1.什麼是AOP? AOP(Aspect-Oriented Programming, 面向切麵編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充,它的主要編程對象是切麵(aspect), 而切麵模塊化橫切關註點.在應用 A ...


1.什麼是AOP?

AOP(Aspect-Oriented Programming, 面向切麵編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充,它的主要編程對象是切麵(aspect), 而切麵模塊化橫切關註點.在應用 AOP 編程時, 仍然需要定義公共功能, 但可以明確的定義這個功能在哪裡, 以什麼方式應用, 並且不必修改受影響的類. 這樣一來橫切關註點就被模塊化到特殊的對象(切麵)里。

2.為什麼需要AOP?

越來越多的非業務需求(日誌和驗證等)加入後, 原有的業務方法急劇膨脹.  每個方法在處理核心邏輯的同時還必須兼顧其他多個關註點. 以日誌需求為例, 只是為了滿足這個單一需求, 就不得不在多個模塊(方法)里多次重覆相同的日誌代碼. 如果日誌需求發生變化, 必須修改所有模塊。

上述問題解決的方法就是使用動態代理,代理設計模式的原理是使用一個代理將對象包裝起來, 然後用該代理對象取代原始對象. 任何對原始對象的調用都要通過代理. 代理對象決定是否以及何時將方法調用轉到原始對象上。

使用AOP的好處是:

  • 每個事物邏輯位於一個位置, 代碼不分散, 便於維護和升級
  • 業務模塊更簡潔, 只包含核心業務代碼.

 

3.AOP術語

  • 切麵(Aspect):  橫切關註點(跨越應用程式多個模塊的功能)被模塊化的特殊對象;
  • 通知(Advice):  切麵必須要完成的工作;
  • 目標(Target): 被通知的對象;
  • 代理(Proxy): 向目標對象應用通知之後創建的對象;
  • 連接點(Joinpoint):程式執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等。連接點由兩個信息確定:方法表示的程式執行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執行前的連接點,執行點為 ArithmethicCalculator#add(); 方位為該方法執行前的位置;
  • 切點(pointcut):每個類都擁有多個連接點:例如 ArithmethicCalculator 的所有方法實際上都是連接點,即連接點是程式類中客觀存在的事務。AOP 通過切點定位到特定的連接點。類比:連接點相當於資料庫中的記錄,切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過 org.springframework.aop.Pointcut 介面進行描述,它使用類和方法作為連接點的查詢條件。

4.如何使用AOP?

AspectJ:Java 社區里最完整最流行的 AOP 框架.在 Spring2.0 以上版本中, 可以使用基於 AspectJ 註解或基於 XML 配置的 AOP。

4.1 在Spring中啟用AspectJ註解支持

(1)在classpath下添加jar包

要在Spring應用中使用AspectJ註解,需要添加的jar包有(包含Spring的基礎jar包):

  • com.springsource.org.aopalliance-1.0.0.jar
  • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  • commons-logging-1.1.3.jar
  • spring-aop-4.0.0.RELEASE.jar
  • spring-aspects-4.0.0.RELEASE.jar
  • spring-beans-4.0.0.RELEASE.jar
  • spring-context-4.0.0.RELEASE.jar
  • spring-core-4.0.0.RELEASE.jar
  • spring-expression-4.0.0.RELEASE.jar

(2)在配置文件中加入AOP的命名空間

(3)要在 Spring IOC 容器中啟用 AspectJ 註解支持, 只要在 Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy>,當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時, 會自動為與 AspectJ 切麵匹配的 Bean 創建代理.

4.2 用AspectJ註解聲明切麵

(1)要在Spring中聲明AspectJ切麵,需要在IOC容器中將切麵聲明為Bean實例,即加入@Component註解;

(2)在AspectJ註解中,切麵是一個帶有@Aspect註解的Java類,即加入@Aspect註解;

4.3 在類中聲明各種通知

(1)聲明一個方法;

(2)在方法前加入通知註解。

5.AspectJ 支持 5 種類型的通知註解

  • @Before: 前置通知, 在方法執行之前執行
  • @After: 後置通知, 在方法執行之後執行
  • @AfterRunning: 返回通知, 在方法返回結果之後執行
  • @AfterThrowing: 異常通知, 在方法拋出異常之後
  • @Around: 環繞通知, 圍繞著方法執行

5.1 前置通知

在方法執行之前執行的通知。前置通知使用 @Before 註解, 並將切入點表達式的值作為註解值。

示例代碼:

定義介面:ArithmeticCalculator.java

package com.java.spring.aop.impl;

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);
}

介面的實現類:ArithmeticCalculatorImpl.java

package com.java.spring.aop.impl;
import org.springframework.stereotype.Component;

@Component("arithmetiCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
	@Override
	public int add(int i, int j) {
		int result = i+j;
		return result;
	}
	@Override
	public int sub(int i, int j) {
		int result = i-j;
		return result;
	}
	@Override
	public int mul(int i, int j) {
		int result = i*j;
		return result;
	}
	@Override
	public int div(int i, int j) {
		int result = i/j;
		return result;
	}
}

日誌LoggingAspect.java

package com.java.spring.aop.impl;

import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
	@Before("execution(public int com.java.spring.aop.impl.ArithmeticCalculator.*(int, int))")
	public void beforeMethod(JoinPoint joinpoint){
		String methodName=joinpoint.getSignature().getName();
		List<Object> args=Arrays.asList(joinpoint.getArgs());
		System.out.println("The method "+methodName+" begins with args "+args);
	}
	
}

在applicationContext.xml中進行配置:

<context:component-scan base-package="com.java.spring.aop.impl"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

測試:

Main.java

package com.java.spring.aop.impl;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
	public static void main(String[] args){
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		ArithmeticCalculator ac=(ArithmeticCalculator) ctx.getBean("arithmetiCalculator");
		int result1=ac.add(123, 10);
		System.out.println(result1);
		int result2=ac.sub(123, 10);
		System.out.println(result2);
	}
}

運行後輸出:

The method add begins with args [123, 10]
133
The method sub begins with args [123, 10]
113

(1)LoggingAspect.java中,

@Before("execution(public int com.java.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public void beforeMethod(JoinPoint joinpoint){...}

標示beforeMethod()方法是前置通知,切點表達式表示執行ArithmeticCalculator介面中參數為兩個int類型,並且方法修飾符為public和返回值為int類型的所有方法。

最典型的切入點表達式時根據方法的簽名來匹配各種方法:

  • execution * com.java.spring.aop.impl.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中聲明的所有方法,第一個 * 代表任意修飾符及任意返回值. 第二個 * 代表任意方法. .. 匹配任意數量的參數. 若目標類與介面與該切麵在同一個包中, 可以省略包名.
  • execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 介面的所有公有方法.
  • execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 類型數值的方法
  • execution public double ArithmeticCalculator.*(double, ..): 匹配第一個參數為 double 類型的方法, .. 匹配任意數量任意類型的參數
  • execution public double ArithmeticCalculator.*(double, double): 匹配參數類型為 double, double 類型的方法.

(2)讓通知訪問當前連接點的細節。可以在通知方法中聲明一個類型為 JoinPoint 的參數. 然後就能訪問鏈接細節. 如方法名稱和參數值.

String methodName=joinpoint.getSignature().getName();
List<Object> args=Arrays.asList(joinpoint.getArgs());

5.2 後置通知

後置通知是在連接點完成之後執行的, 即連接點返回結果或者拋出異常的時候. 一個切麵可以包括一個或者多個通知。

@After("execution(public int com.java.spring.aop.impl.ArithmeticCalculator.*(int, int))")
	public void afterMethod(JoinPoint joinpoint){
		String methodName=joinpoint.getSignature().getName();
		List<Object> args=Arrays.asList(joinpoint.getArgs());
		System.out.println("The method "+methodName+" ends with args "+args);
	}

5.3 返回通知

無論連接點是正常返回還是拋出異常, 後置通知都會執行. 如果只想在連接點返回的時候記錄日誌, 應使用返回通知代替後置通知,返回通知是可以訪問到方法的返回值的。

在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 註解中, 就可以訪問連接點的返回值. 該屬性的值即為用來傳入返回值的參數名稱. 而且必須在通知方法的簽名中添加一個同名參數. 在運行時, Spring AOP 會通過這個參數傳遞返回值.

@AfterReturning(value="execution(public int com.java.spring.aop.impl.ArithmeticCalculator.*(int, int))",
			returning="result")
	public void reutrnMethod(JoinPoint joinpoint,Object result){
		String methodName=joinpoint.getSignature().getName();
		List<Object> args=Arrays.asList(joinpoint.getArgs());
		System.out.println("The method "+methodName+" ends with args "+args+"and the result is "+result);
	}

5.4 異常通知

只在連接點拋出異常時才執行異常通知。將 throwing 屬性添加到 @AfterThrowing 註解中, 也可以訪問連接點拋出的異常. Throwable 是所有錯誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯誤和異常.如果只對某種特殊的異常類型感興趣, 可以將參數聲明為其他異常的參數類型. 然後通知就只在拋出這個類型及其子類的異常時才被執行。

@AfterThrowing(value="execution(public int com.java.spring.aop.impl.ArithmeticCalculator.*(int, int))",
			throwing="e")
	public void afterThrowing(JoinPoint joinpoint,Exception e){
		String methodName=joinpoint.getSignature().getName();
		List<Object> args=Arrays.asList(joinpoint.getArgs());
		System.out.println("The method "+methodName+" occurs "+args+e);
	}

5.5 環繞通知

環繞通知是所有通知類型中功能最為強大的, 能夠全面地控制連接點. 甚至可以控制是否執行連接點。對於環繞通知來說, 連接點的參數類型必須是 ProceedingJoinPoint ,可以決定是否執行目標方法。它是 JoinPoint 的子介面, 允許控制何時執行, 是否執行連接點。在環繞通知中需要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法. 如果忘記這樣做就會導致通知被執行了, 但目標方法沒有被執行。

@Around("execution(public int com.java.spring.aop.impl.ArithmeticCalculator.*(int, int))")
	public Object aroundMethod(ProceedingJoinPoint pjd){
		Object result=null;
		String methodName=pjd.getSignature().getName();
		try {
			//前置通知
			System.out.println("The method "+methodName+" begins with args "+Arrays.asList(pjd.getArgs()));
			//執行目標方法
			result=pjd.proceed();
			//返回通知
			System.out.println("The method "+"ends with "+result);
		} catch (Throwable e) {
			//異常通知
			System.out.println("The method occurs Exception "+e);
		}
		//後置通知
		System.out.println("The method ends");
		return result;
	}

6.切麵的優先順序

在同一個連接點上應用不止一個切麵時, 除非明確指定, 否則它們的優先順序是不確定的.切麵的優先順序可以通過實現 Ordered 介面或利用 @Order 註解指定.實現 Ordered 介面, getOrder() 方法的返回值越小, 優先順序越高;若使用 @Order 註解, 序號出現在註解中。

@Aspect
@Order(0)
@Component
public class LoggingAspect {}

7.重用切入點表達式

在 AspectJ 切麵中, 可以通過 @Pointcut 註解將一個切入點聲明成簡單的方法. 切入點的方法體通常是空的。後面的其他通知直接使用方法名來引用當前的接入點表達式。

切入點方法的訪問控制符同時也控制著這個切入點的可見性. 如果切入點要在多個切麵中共用, 最好將它們集中在一個公共的類中. 在這種情況下, 它們必須被聲明為 public. 在引入這個切入點時, 必須將類名也包括在內. 如果類沒有與這個切麵放在同一個包中, 還必須包含包名.

//定義一個方法,用於聲明一個切入點表達式
	@Pointcut("execution(public int com.java.spring.aop.impl.ArithmeticCalculator.*(int, int))")
	public void declareJointPointExpression(){}
	@Before("declareJointPointExpression()")
	public void beforeMethod(JoinPoint joinpoint){
		String methodName=joinpoint.getSignature().getName();
		List<Object> args=Arrays.asList(joinpoint.getArgs());
		System.out.println("The method "+methodName+" begins with args "+args);
	}

 


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

-Advertisement-
Play Games
更多相關文章
  • Java線程通信方法 0、(why)每個線程都有自己的棧空間,我們要線程之間進行交流,合作共贏。 1、synchronized和volatile關鍵字 a) 看下麵的synchronized關鍵字 b) 看下麵的volatile關鍵字 2、等待/通知機制:一個線程A調用對象的wait()方法,另一個 ...
  • 一.遞歸函數的弊端 遞歸函數雖然編寫時用很少的代碼完成了龐大的功能,但是它的弊端確實非常明顯的,那就是時間與空間的消耗。 用一個斐波那契數列來舉例 import time #@lru_cache(20) def fibonacci(n): if n < 2: return 1 else: retur ...
  • 前言 本篇緊接著spring入門詳細教程(一),建議閱讀本篇前,先閱讀第一篇。鏈接如下: Spring入門詳細教程(一) https://www.cnblogs.com/jichi/p/10165538.html 一、spring註入方式 1、set方法註入 2、構造方法註入 3、p名稱空間註入 4 ...
  • JUnit常用單元測試註解介紹及代碼演示 by:授客 QQ:1033553122 1. 測試環境 1 2. 基礎概念 1 3. 常用Annotation 1 4. 運行環境配置 3 maven配置 3 Eclipse maven運行環境配置 4 更新項目 5 5. 單元測試實踐 7 被測類Binar ...
  • 1. 安裝 2. 項目目錄 3. settings配置 4. celery模塊 python 在task文件中 from __future__ import absolute_import, unicode_literals import os from celery import Celery s ...
  • 一、BOM對象 1,window對象 所有瀏覽器都支持window對象,從概念上講:一個HTML文檔對應一個window對象,從功能上講:控制瀏覽器視窗的,從使用上講:window對象不需要創建對象,直接使用即可 2,window對象方法 3,方法的使用 3.1彈窗方法,警告窗alert、確認窗co ...
  • Scrapy Scrapy 是一個位了爬取網站數據,提取數據結構性數據而編寫的應用框架,少量代碼,就能快速爬取,使用了Twisted 非同步網路框架,加快我們下載速度! 工作流程 製作 Scrapy 爬蟲 一共需要4步: 新建項目 (scrapy startproject xxx):新建一個新的爬蟲項 ...
  • Guava官方文檔 https://github.com/google/guava/wiki/CollectionUtilitiesExplained 官方文檔這樣描述: " " addresses the common case of having a bunch of objects that ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...