網(wǎng)站首頁 編程語言 正文
什么是AOP?
AOP (Aspect Orient Programming),直譯過來就是 面向切面編程。AOP 是一種編程思想,是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充。面向?qū)ο缶幊虒⒊绦虺橄蟪筛鱾€(gè)層次的對(duì)象,而面向切面編程是將程序抽象成各個(gè)切面。從《Spring實(shí)戰(zhàn)(第4版)》圖書中扒了一張圖:
從該圖可以很形象地看出,所謂切面,相當(dāng)于應(yīng)用對(duì)象間的橫切點(diǎn),我們可以將其單獨(dú)抽象為單獨(dú)的模塊。
為什么需要AOP?
想象下面的場(chǎng)景,開發(fā)中在多個(gè)模塊間有某段重復(fù)的代碼,我們通常是怎么處理的?顯然,沒有人會(huì)靠“復(fù)制粘貼”吧。在傳統(tǒng)的面向過程編程中,我們也會(huì)將這段代碼,抽象成一個(gè)方法,然后在需要的地方分別調(diào)用這個(gè)方法,這樣當(dāng)這段代碼需要修改時(shí),我們只需要改變這個(gè)方法就可以了。然而需求總是變化的,有一天,新增了一個(gè)需求,需要再多出做修改,我們需要再抽象出一個(gè)方法,然后再在需要的地方分別調(diào)用這個(gè)方法,又或者我們不需要這個(gè)方法了,我們還是得刪除掉每一處調(diào)用該方法的地方。實(shí)際上涉及到多個(gè)地方具有相同的修改的問題我們都可以通過 AOP 來解決。
AOP術(shù)語
AOP 領(lǐng)域中的特性術(shù)語:
- 通知(Advice): AOP 框架中的增強(qiáng)處理。通知描述了切面何時(shí)執(zhí)行以及如何執(zhí)行增強(qiáng)處理。
- 連接點(diǎn)(join point): 連接點(diǎn)表示應(yīng)用執(zhí)行過程中能夠插入切面的一個(gè)點(diǎn),這個(gè)點(diǎn)可以是方法的調(diào)用、異常的拋出。在 Spring AOP 中,連接點(diǎn)總是方法的調(diào)用。
- 切點(diǎn)(PointCut): 可以插入增強(qiáng)處理的連接點(diǎn)。
- 切面(Aspect): 切面是通知和切點(diǎn)的結(jié)合。
- 引入(Introduction):引入允許我們向現(xiàn)有的類添加新的方法或者屬性。
- 織入(Weaving): 將增強(qiáng)處理添加到目標(biāo)對(duì)象中,并創(chuàng)建一個(gè)被增強(qiáng)的對(duì)象,這個(gè)過程就是織入。
通過注解聲明5種通知類型
Spring AOP 中有 5 中通知類型,分別如下:
本章中主要以@Before、@After和@Around為例展示AOP的增強(qiáng)方式。
首先引入依賴,這里只放aop的依賴,其它的依賴請(qǐng)根據(jù)自己的實(shí)際情況引入:
<!-- 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>
接著新建一個(gè)切面類TesstAspect,并且定義3個(gè)切點(diǎn),就是后面要測(cè)試的3個(gè)切點(diǎn):
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í)行的連接點(diǎn),第一個(gè)*表示匹配任意的方法返回值
@Before
先測(cè)試第一個(gè)增強(qiáng)方法,在切點(diǎn)方法之前執(zhí)行,因?yàn)槭呛唵螠y(cè)試,就只打印一下日志就好了:
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("當(dāng)前線程: {} 開始執(zhí)行查詢前任務(wù)...", Thread.currentThread().getName());
}
}
啟動(dòng)項(xiàng)目,進(jìn)入swagger的頁面調(diào)用接口測(cè)試:
?
?調(diào)用接口后,在控制臺(tái)可以看到日志打印的先后順序,先執(zhí)行的@Before里的增強(qiáng)方法再執(zhí)行的service里的方法:
@After
?接著測(cè)試@After,為了更好的展示增強(qiáng)方式,這次利用JoinPoint獲取參數(shù)。
說明:Joinpoint是AOP的連接點(diǎn)。一個(gè)連接點(diǎn)代表一個(gè)被代理的方法。
為了獲取參數(shù)的方法能夠復(fù)用,這里新建一個(gè)工具類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) {
// 獲取所有參數(shù)的值
Object[] args = joinPoint.getArgs();
// 獲取方法簽名
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
// 在方法簽名中獲取所有參數(shù)的名稱
String[] parameterNames = methodSignature.getParameterNames();
// 根據(jù)參數(shù)名稱拿到下標(biāo), 參數(shù)值的數(shù)組和參數(shù)名稱的數(shù)組下標(biāo)是一一對(duì)應(yīng)的
int index = ArrayUtils.indexOf(parameterNames, paramName);
// 在參數(shù)數(shù)組中取出下標(biāo)對(duì)應(yīng)參數(shù)值
Object obj = args[index];
if (obj == null) {
return null;
}
// 將object對(duì)象轉(zhuǎn)為Class返回
if (clazz.isInstance(obj)) {
return clazz.cast(obj);
}
return (T) obj;
}
}
接著編寫@After的增強(qiáng)方法,在切點(diǎn)方法之后執(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("當(dāng)前線程: {} 開始執(zhí)行查詢前任務(wù)...", Thread.currentThread().getName());
}
@After("pointCut2()")
public void doAfter(JoinPoint joinPoint) {
Integer index = JoinPointUtils.getParamByName(joinPoint, "index", Integer.class);
log.info("當(dāng)前線程: {}執(zhí)行完任務(wù),請(qǐng)求參數(shù)值: {}", Thread.currentThread().getName(), index);
}
}
為了方便理解這里獲取的參數(shù),下面放一下這里切入的方法:
?然后一樣的流程,啟動(dòng)項(xiàng)目,在swagger頁面里調(diào)用接口:
?
@Around?
前面2個(gè)例子一個(gè)是在切點(diǎn)之前執(zhí)行,一個(gè)是在切點(diǎn)之后執(zhí)行,如果項(xiàng)目中我們想要記錄一個(gè)sql執(zhí)行的耗時(shí)時(shí)間,應(yīng)該怎么做?
@Around環(huán)繞通知:它集成了@Before、@AfterReturing、@AfterThrowing、@After四大通知。需要注意的是,它和其他四大通知注解最大的不同是需要手動(dòng)進(jìn)行接口內(nèi)方法的反射后才能執(zhí)行接口中的方法,換言之,@Around其實(shí)就是一個(gè)動(dòng)態(tài)代理。
利用@Around的話,就可以編寫一個(gè)方法,切入多個(gè)切點(diǎn)記錄耗時(shí)了:
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("當(dāng)前線程: {} 開始執(zhí)行查詢前任務(wù)...", Thread.currentThread().getName());
}
@After("pointCut2()")
public void doAfter(JoinPoint joinPoint) {
Integer index = JoinPointUtils.getParamByName(joinPoint, "index", Integer.class);
log.info("當(dāng)前線程: {}執(zhí)行完任務(wù),請(qǐng)求參數(shù)值: {}", Thread.currentThread().getName(), index);
}
@Around("pointCut3()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
// 調(diào)用執(zhí)行目標(biāo)方法(result為目標(biāo)方法執(zhí)行結(jié)果),必須有此行代碼才會(huì)執(zhí)行目標(biāo)調(diào)用的方法(等價(jià)于@befor+@after),否則只會(huì)執(zhí)行一次之前的(等價(jià)于@before)
Object result = pjp.proceed();
long end = System.currentTimeMillis();
log.info(pjp.getTarget().getClass().getSimpleName() + "->" + pjp.getSignature().getName() + " 耗費(fèi)時(shí)間:" + (end - start) + "毫秒");
return result;
}
}
啟動(dòng)項(xiàng)目,調(diào)用接口,看控制臺(tái)輸出:
?
原文鏈接:https://blog.csdn.net/wl_Honest/article/details/125582289
相關(guān)推薦
- 2022-07-27 SQL解決未能刪除約束問題drop?constraint_MsSql
- 2022-10-06 Android?Activity啟動(dòng)流程刨析_Android
- 2022-10-12 Xshell7遠(yuǎn)程連接失敗(connection?failed)的問題解決_Linux
- 2022-08-12 windows?bat批處理判斷電腦服務(wù)是否運(yùn)行的問題_DOS/BAT
- 2022-05-20 C++實(shí)現(xiàn)簡單學(xué)生成績管理系統(tǒng)_C 語言
- 2023-03-16 Python?asyncio異步編程簡單實(shí)現(xiàn)示例_python
- 2022-07-20 字符串和內(nèi)存函數(shù)
- 2022-09-12 Python詳解復(fù)雜CSV文件處理方法_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支