Spring?AOP中三種增強方式的示例詳解
什么是AOP
AOP (Aspect Orient Programming),直譯過來就是 面向切面編程。AOP 是一種編程思想,是面向對象編程(OOP)的一種補充。面向對象編程將程序抽象成各個層次的對象,而面向切面編程是將程序抽象成各個切面。從《Spring實戰(zhàn)(第4版)》圖書中扒了一張圖:
從該圖可以很形象地看出,所謂切面,相當于應用對象間的橫切點,我們可以將其單獨抽象為單獨的模塊。
為什么需要AOP
想象下面的場景,開發(fā)中在多個模塊間有某段重復的代碼,我們通常是怎么處理的?顯然,沒有人會靠“復制粘貼”吧。在傳統的面向過程編程中,我們也會將這段代碼,抽象成一個方法,然后在需要的地方分別調用這個方法,這樣當這段代碼需要修改時,我們只需要改變這個方法就可以了。然而需求總是變化的,有一天,新增了一個需求,需要再多出做修改,我們需要再抽象出一個方法,然后再在需要的地方分別調用這個方法,又或者我們不需要這個方法了,我們還是得刪除掉每一處調用該方法的地方。實際上涉及到多個地方具有相同的修改的問題我們都可以通過 AOP 來解決。
AOP術語
AOP 領域中的特性術語:
- 通知(Advice): AOP 框架中的增強處理。通知描述了切面何時執(zhí)行以及如何執(zhí)行增強處理。
- 連接點(join point): 連接點表示應用執(zhí)行過程中能夠插入切面的一個點,這個點可以是方法的調用、異常的拋出。在 Spring AOP 中,連接點總是方法的調用。
- 切點(PointCut): 可以插入增強處理的連接點。
- 切面(Aspect): 切面是通知和切點的結合。
- 引入(Introduction):引入允許我們向現有的類添加新的方法或者屬性。
- 織入(Weaving): 將增強處理添加到目標對象中,并創(chuàng)建一個被增強的對象,這個過程就是織入。
通過注解聲明5種通知類型
Spring AOP 中有 5 中通知類型,分別如下:
本章中主要以@Before、@After和@Around為例展示AOP的增強方式。
首先引入依賴,這里只放aop的依賴,其它的依賴請根據自己的實際情況引入:
<!-- aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.18.RELEASE</version> </dependency>
接著新建一個切面類TesstAspect,并且定義3個切點,就是后面要測試的3個切點:
package com.wl.standard.aop.aspect; import com.wl.standard.util.JoinPointUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * @author wl * @date 2022/7/2 16:08 */ @Slf4j @Component @Aspect public class TestAspect { @Pointcut("execution(public * com.wl.standard.service.TravelRecordService.getAllRecord(..))") public void pointCut1(){}; @Pointcut("execution(public * com.wl.standard.service.CityRailService.getTopRail(..))") public void pointCut2(){}; @Pointcut("execution(public * com.wl.standard.service.CityGdpService.compareGDP(..))") public void pointCut3(){}; }
備注:execution():用于匹配方法執(zhí)行的連接點,第一個*表示匹配任意的方法返回值
@Before
先測試第一個增強方法,在切點方法之前執(zhí)行,因為是簡單測試,就只打印一下日志就好了:
package com.wl.standard.aop.aspect; import com.wl.standard.util.JoinPointUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * @author wl * @date 2022/7/2 16:08 */ @Slf4j @Component @Aspect public class TestAspect { @Pointcut("execution(public * com.wl.standard.service.TravelRecordService.getAllRecord(..))") public void pointCut1(){}; @Pointcut("execution(public * com.wl.standard.service.CityRailService.getTopRail(..))") public void pointCut2(){}; @Pointcut("execution(public * com.wl.standard.service.CityGdpService.compareGDP(..))") public void pointCut3(){}; @Before("pointCut1()") public void doBefore(JoinPoint joinPoint) { log.info("當前線程: {} 開始執(zhí)行查詢前任務...", Thread.currentThread().getName()); } }
啟動項目,進入swagger的頁面調用接口測試:
調用接口后,在控制臺可以看到日志打印的先后順序,先執(zhí)行的@Before里的增強方法再執(zhí)行的service里的方法:
@After
接著測試@After,為了更好的展示增強方式,這次利用JoinPoint獲取參數。
說明:Joinpoint是AOP的連接點。一個連接點代表一個被代理的方法。
為了獲取參數的方法能夠復用,這里新建一個工具類JoinPointUtils:
package com.wl.standard.util; import org.apache.commons.lang.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.MethodSignature; /** * JoinPoint 工具類 * @author wl * @date 2022/7/2 21:55 */ public class JoinPointUtils { public static <T> T getParamByName(JoinPoint joinPoint, String paramName, Class<T> clazz) { // 獲取所有參數的值 Object[] args = joinPoint.getArgs(); // 獲取方法簽名 Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; // 在方法簽名中獲取所有參數的名稱 String[] parameterNames = methodSignature.getParameterNames(); // 根據參數名稱拿到下標, 參數值的數組和參數名稱的數組下標是一一對應的 int index = ArrayUtils.indexOf(parameterNames, paramName); // 在參數數組中取出下標對應參數值 Object obj = args[index]; if (obj == null) { return null; } // 將object對象轉為Class返回 if (clazz.isInstance(obj)) { return clazz.cast(obj); } return (T) obj; } }
接著編寫@After的增強方法,在切點方法之后執(zhí)行:
package com.wl.standard.aop.aspect; import com.wl.standard.util.JoinPointUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * @author wl * @date 2022/7/2 16:08 */ @Slf4j @Component @Aspect public class TestAspect { @Pointcut("execution(public * com.wl.standard.service.TravelRecordService.getAllRecord(..))") public void pointCut1(){}; @Pointcut("execution(public * com.wl.standard.service.CityRailService.getTopRail(..))") public void pointCut2(){}; @Pointcut("execution(public * com.wl.standard.service.CityGdpService.compareGDP(..))") public void pointCut3(){}; @Before("pointCut1()") public void doBefore(JoinPoint joinPoint) { log.info("當前線程: {} 開始執(zhí)行查詢前任務...", Thread.currentThread().getName()); } @After("pointCut2()") public void doAfter(JoinPoint joinPoint) { Integer index = JoinPointUtils.getParamByName(joinPoint, "index", Integer.class); log.info("當前線程: {}執(zhí)行完任務,請求參數值: {}", Thread.currentThread().getName(), index); } }
為了方便理解這里獲取的參數,下面放一下這里切入的方法:
然后一樣的流程,啟動項目,在swagger頁面里調用接口:
@Around
前面2個例子一個是在切點之前執(zhí)行,一個是在切點之后執(zhí)行,如果項目中我們想要記錄一個sql執(zhí)行的耗時時間,應該怎么做?
@Around環(huán)繞通知:它集成了@Before、@AfterReturing、@AfterThrowing、@After四大通知。需要注意的是,它和其他四大通知注解最大的不同是需要手動進行接口內方法的反射后才能執(zhí)行接口中的方法,換言之,@Around其實就是一個動態(tài)代理。
利用@Around的話,就可以編寫一個方法,切入多個切點記錄耗時了:
package com.wl.standard.aop.aspect; import com.wl.standard.util.JoinPointUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * @author wl * @date 2022/7/2 16:08 */ @Slf4j @Component @Aspect public class TestAspect { @Pointcut("execution(public * com.wl.standard.service.TravelRecordService.getAllRecord(..))") public void pointCut1(){}; @Pointcut("execution(public * com.wl.standard.service.CityRailService.getTopRail(..))") public void pointCut2(){}; @Pointcut("execution(public * com.wl.standard.service.CityGdpService.compareGDP(..))") public void pointCut3(){}; @Before("pointCut1()") public void doBefore(JoinPoint joinPoint) { log.info("當前線程: {} 開始執(zhí)行查詢前任務...", Thread.currentThread().getName()); } @After("pointCut2()") public void doAfter(JoinPoint joinPoint) { Integer index = JoinPointUtils.getParamByName(joinPoint, "index", Integer.class); log.info("當前線程: {}執(zhí)行完任務,請求參數值: {}", Thread.currentThread().getName(), index); } @Around("pointCut3()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); // 調用執(zhí)行目標方法(result為目標方法執(zhí)行結果),必須有此行代碼才會執(zhí)行目標調用的方法(等價于@befor+@after),否則只會執(zhí)行一次之前的(等價于@before) Object result = pjp.proceed(); long end = System.currentTimeMillis(); log.info(pjp.getTarget().getClass().getSimpleName() + "->" + pjp.getSignature().getName() + " 耗費時間:" + (end - start) + "毫秒"); return result; } }
啟動項目,調用接口,看控制臺輸出:
以上就是Spring AOP中三種增強方式的示例詳解的詳細內容,更多關于Spring AOP增強方式的資料請關注腳本之家其它相關文章!
相關文章
Windows下使用IDEA搭建Hadoop開發(fā)環(huán)境的詳細方法
這篇文章主要介紹了Windows下使用IDEA搭建Hadoop開發(fā)環(huán)境,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12