Spring框架是我們使用比較多的一個框架,而AOP又是Spring的核心特性之一,本篇文章將介紹一下AOP的切點表達式、通知等特性及如何使用Spring AOP。
AOP(Aspect-Oriented Programming,面向切面編程) 是一種編程范式,旨在將橫切關注點與核心業務邏輯相分離,以提高代碼的模塊化性、可維護性和復用性。
在傳統的面向對象編程中,程序的功能被模塊化為類和方法,但某些功能可能會跨越多個類和方法,如日志記錄、事務管理、安全控制等,這些功能不屬于核心業務邏輯,但又必須在多個地方重復使用,導致代碼重復和耦合性增加。
AOP提供了一種機制,可以將這些橫切關注點單獨定義,并在需要的地方插入到應用程序中,而不必修改核心業務邏輯。
AspectJ是一個面向切面的框架,它擴展了Java語言。AspectJ定義了AOP(面向切面編程) 語法,并擁有一個專門的編譯器,用于生成遵守Java字節編碼規范的Class文件。
AspectJ可以單獨使用,也可以整合到其他框架中。當單獨使用AspectJ時,需要使用專門的編譯器ajc。AspectJ屬于靜態織入,通過修改代碼來實現,包括編譯期織入等多種織入時機。
Spring集成AspectJ,可以在Spring中方便的使用AOP。
Spring AOP核心概念主要包括以下幾個方面:
這些核心概念共同構成了AOP的基礎,使得我們能夠模塊化地處理橫切關注點,從而提高代碼的可維護性和可重用性。
Pointcut 表達式 是用來定義切入點的規則,它決定了哪些連接點(方法調用或方法執行)將會被通知所影響。在 Spring AOP 中,Pointcut 表達式通常由以下幾種規則和通配符組成:
帶有 @ 符的切點表達式都是需要指定注解的連接點。
這些規則可以通過邏輯運算符(如 &&、||、! )進行組合,以實現更復雜的 Pointcut 匹配規則。我們可以根據自己的需求,靈活地使用這些規則來定義切入點表達式,實現對目標方法的精確匹配和監控。
execution() 表達式使用的比較多,最復雜的一個表達式,這里重點介紹一下。
execution() 表達式的語法結構如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
其中,各部分的含義如下:
execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
execution(* com.example.service.MyService.myMethod(String, int))
總的來說,execution() 方法提供了一種靈活且強大的方式來定義切點表達式,從而精確定位需要添加通知的目標方法。
圖片
在 Spring AOP 中,通知(Advice)是在切入點(Pointcut)上執行的代碼。Spring 提供了幾種類型的通知,每種類型都對應著在連接點執行前、執行后或拋出異常時執行的不同代碼邏輯。這些通知對應著不同的注解,常用的通知注解包括:
value:要綁定的切點或者切點表達式。
argNames: 用于指定連接點表達式中方法參數的名稱,以便在通知方法中通過參數名來獲取方法參數的值。這樣可以在前置通知中訪問和處理方法參數的具體數值。該屬性即使不指定也能獲取參數。
pointcut:作用和value屬性一樣,當指定pointcut時,會覆蓋value屬性的值。
returning:方法返回的結果將被綁定到此參數名,可以在通知中訪問方法的返回值。
@AfterThrowing: 在方法拋出異常后執行的通知。它的屬性前3個和 @AfterReturning注解一樣,多了1個屬性:
throwing:指定方法拋出的異常將被綁定到此參數名,可以在通知中訪問方法拋出的異常。
@After: 在方法執行后(無論成功或失敗)執行的通知。屬性同 @Before 注解。
@Around: 環繞通知,能夠在方法執行前后都可以進行操作,具有最大的靈活性。屬性同 @Before 注解。
通知的執行順序為: @Around -> @Before -> @AfterReturning(不拋異常情況) 或者 @AfterThrowing(拋異常情況) -> @After
這些通知注解可以與 Pointcut 表達式結合使用,實現對目標方法的攔截和處理。通過選擇合適的通知類型,開發者可以根據需求在不同的時間點插入自定義的邏輯,實現對方法調用的控制和增強。
講了那么多概念性的東西,下面來看怎么使用Spring AOP。
在Spring 中使用AOP也很簡單,主要分3步:
我這里使用的是Springboot 3.1.5、jdk 17,如果是Springboot低版本的可能需要引入 spring-boot-starter-aop 依賴,高版本的AOP已經包含在spring-boot-starter-web依賴中了:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>
Spring官網中介紹,使用Spring AOP要在啟動類或者配置類中加上 @EnableAspectJAutoProxy 注解開啟 AspectJ 注解的支持,在我使用的這個版本中并不需要,如果你的項目中切面未生效可以嘗試使用該注解。
定義一個接口,下面用于對這個接口及其實現類進行攔截:
public interface AopService { /** * 兩數除法 * @param a * @param b * @return */ BigDecimal divide(BigDecimal a, BigDecimal b); /** * 兩數加法 * @param a * @param b * @return */ BigDecimal add(BigDecimal a, BigDecimal b);}
@Servicepublic class MyAopServiceImpl implements AopService{ /** * 兩數除法 * * @param a * @param b * @return */ @Override public BigDecimal divide(BigDecimal a, BigDecimal b) { return a.divide(b , RoundingMode.UP); } /** * 兩數加法 * * @param a * @param b * @return */ @Override public BigDecimal add(BigDecimal a, BigDecimal b) { return a.add(b); }}
新建一個類,在類上加上@Aspect 注解,標記該類為切面。
@Component@Aspectpublic class AspectComponent {}
在切面中使用@Pointcut注解定義切點表達式,然后在通知注解中使用定義好的切點。在該示例中主要對AopService#divide()方法進行攔截。
@Component@Aspectpublic class AspectComponent { /** * 匹配AopService接口的divide方法 */ @Pointcut("execution(* site.suncodernote.aop.AopService.divide(..))") void dividePointCut(){ } /** * 匹配AopService接口的divide方法 */ @Pointcut("within(site.suncodernote.aop.AopService+)") void withinPointCut(){ } /** * 匹配AopService接口的add方法 或者 divide方法 */ @Pointcut("execution(* site.suncodernote.aop.AopService.add(..)) || execution(* site.suncodernote.aop.AopService.divide(..))") void addOrDividePointCut(){ } @Before("dividePointCut()") public void beforeDivide(JoinPoint joinPoint){ System.out.println("---------------------@Before----------------"); printJoinPoint(joinPoint); } @After("dividePointCut()") public void afterDivide(JoinPoint joinPoint){ System.out.println("---------------------@After----------------"); printJoinPoint(joinPoint); } @AfterReturning(pointcut = "dividePointCut()" , returning = "result") public void afterReturningDivide(JoinPoint joinPoint , BigDecimal result){ System.out.println("---------------------@AfterReturning----------------"); System.out.println("返回結果="+result); printJoinPoint(joinPoint); } @AfterThrowing(pointcut = "dividePointCut()" , throwing = "e") public void afterThrowingDivide(JoinPoint joinPoint ,Exception e){ System.out.println("---------------------@AfterThrowing----------------"); System.out.println("異常:"+e.getMessage()); printJoinPoint(joinPoint); } @Around("dividePointCut()") public Object aroundDivide(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("---------------------@Around----------------"); printJoinPoint(joinPoint); Object[] args = joinPoint.getArgs(); Object result = null; try { //執行方法 result = joinPoint.proceed(args); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("返回值:"+result); return result; } private void printJoinPoint(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); System.out.println("方法名:"+signature.getName()); System.out.println("方法參數:"+ Arrays.toString(args)); System.out.println(); }}
寫個簡單的單元測試,調用AopService#divide()方法,然后看一下輸出結果。
@SpringBootTestpublic class AOPTest { @Resource private AopService aopService; @Test public void testAOP() { BigDecimal a = new BigDecimal(1); BigDecimal b = new BigDecimal(2);// aopService.add(a, b); aopService.divide(a, b); }}
測試結果:
---------------------@Around----------------方法名:divide方法參數:[1, 2]---------------------@Before----------------方法名:divide方法參數:[1, 2]---------------------@AfterReturning----------------返回結果=1方法名:divide方法參數:[1, 2]---------------------@After----------------方法名:divide方法參數:[1, 2]返回值:1
從測試結果中通知執行的順序是按照我們上面所說的執行順序執行的。
本文介紹了Spring AOP的常用的切點表達式、通知注解等,我們可以利用AOP對業務邏輯的各個部分進行隔離,使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高開發的效率。
本文鏈接:http://www.www897cc.com/showinfo-26-82760-0.html輕松上手Spring AOP,掌握切面編程的核心技巧
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com