SpringBoot Aop 詳解和多種使用場(chǎng)景解析
前言
aop面向切面編程,是編程中一個(gè)很重要的思想本篇文章主要介紹的是SpringBoot切面Aop的使用和案例
什么是aop
AOP(Aspect OrientedProgramming):面向切面編程,面向切面編程(也叫面向方面編程),是目前軟件開(kāi)發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。
使用場(chǎng)景
利用AOP可以對(duì)我們邊緣業(yè)務(wù)進(jìn)行隔離,降低無(wú)關(guān)業(yè)務(wù)邏輯耦合性。提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。一般用于日志記錄,性能統(tǒng)計(jì),安全控制,權(quán)限管理,事務(wù)處理,異常處理,資源池管理。使用場(chǎng)景
為什么需要面向切面編程
面向?qū)ο缶幊蹋∣OP)的好處是顯而易見(jiàn)的,缺點(diǎn)也同樣明顯。當(dāng)需要為多個(gè)不具有繼承關(guān)系的對(duì)象添加一個(gè)公共的方法的時(shí)候,例如日志記錄、性能監(jiān)控等,如果采用面向?qū)ο缶幊痰姆椒?,需要在每個(gè)對(duì)象里面都添加相同的方法,這樣就產(chǎn)生了較大的重復(fù)工作量和大量的重復(fù)代碼,不利于維護(hù)。面向切面編程(AOP)是面向?qū)ο缶幊痰难a(bǔ)充,簡(jiǎn)單來(lái)說(shuō)就是統(tǒng)一處理某一“切面”的問(wèn)題的編程思想。如果使用AOP的方式進(jìn)行日志的記錄和處理,所有的日志代碼都集中于一處,不需要再每個(gè)方法里面都去添加,極大減少了重復(fù)代碼。
技術(shù)要點(diǎn)
- 通知(Advice)包含了需要用于多個(gè)應(yīng)用對(duì)象的橫切行為,完全聽(tīng)不懂,沒(méi)關(guān)系,通俗一點(diǎn)說(shuō)就是定義了“什么時(shí)候”和“做什么”。
- 連接點(diǎn)(Join Point)是程序執(zhí)行過(guò)程中能夠應(yīng)用通知的所有點(diǎn)。
- 切點(diǎn)(Poincut)是定義了在“什么地方”進(jìn)行切入,哪些連接點(diǎn)會(huì)得到通知。顯然,切點(diǎn)一定是連接點(diǎn)。
- 切面(Aspect)是通知和切點(diǎn)的結(jié)合。通知和切點(diǎn)共同定義了切面的全部?jī)?nèi)容——是什么,何時(shí),何地完成功能。
- 引入(Introduction)允許我們向現(xiàn)有的類中添加新方法或者屬性。
- 織入(Weaving)是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程,分為編譯期織入、類加載期織入和運(yùn)行期織入。
整合使用
導(dǎo)入依賴
在springboot中使用aop要導(dǎo)aop依賴
<!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
注意這里版本依賴于spring-boot-start-parent父pom中的spring-boot-dependencies
編寫(xiě)攔截的bean
這里我們定義一個(gè)controller用于攔截所有請(qǐng)求的記錄
@RestController
public class AopController {
@RequestMapping("/hello")
public String sayHello(){
System.out.println("hello");
return "hello";
}
}
定義切面
SpringBoot在使用切面的時(shí)候采用@Aspect注解對(duì)POJO進(jìn)行標(biāo)注,該注解表明該類不僅僅是一個(gè)POJO,還是一個(gè)切面容器
定義切點(diǎn)
切點(diǎn)是通過(guò)@Pointcut注解和切點(diǎn)表達(dá)式定義的。
@Pointcut注解可以在一個(gè)切面內(nèi)定義可重用的切點(diǎn)。
由于Spring切面粒度最小是達(dá)到方法級(jí)別,而execution表達(dá)式可以用于明確指定方法返回類型,類名,方法名和參數(shù)名等與方法相關(guān)的部件,并且實(shí)際中,大部分需要使用AOP的業(yè)務(wù)場(chǎng)景也只需要達(dá)到方法級(jí)別即可,因而execution表達(dá)式的使用是最為廣泛的。如圖是execution表達(dá)式的語(yǔ)法:

execution表示在方法執(zhí)行的時(shí)候觸發(fā)。以“”開(kāi)頭,表明方法返回值類型為任意類型。然后是全限定的類名和方法名,“”可以表示任意類和任意方法。對(duì)于方法參數(shù)列表,可以使用“..”表示參數(shù)為任意類型。如果需要多個(gè)表達(dá)式,可以使用“&&”、“||”和“!”完成與、或、非的操作。
定義通知
通知有五種類型,分別是:
- 前置通知(@Before):在目標(biāo)方法調(diào)用之前調(diào)用通知
- 后置通知(@After):在目標(biāo)方法完成之后調(diào)用通知
- 環(huán)繞通知(@Around):在被通知的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義的方法
- 返回通知(@AfterReturning):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知
- 異常通知(@AfterThrowing):在目標(biāo)方法拋出異常之后調(diào)用通知
代碼中定義了三種類型的通知,使用@Before注解標(biāo)識(shí)前置通知,打印“beforeAdvice...”,使用@After注解標(biāo)識(shí)后置通知,打印“AfterAdvice...”,使用@Around注解標(biāo)識(shí)環(huán)繞通知,在方法執(zhí)行前和執(zhí)行之后分別打印“before”和“after”。這樣一個(gè)切面就定義好了,代碼如下:
@Aspect
@Component
public class AopAdvice {
@Pointcut("execution (* com.shangguan.aop.controller.*.*(..))")
public void test() {
}
@Before("test()")
public void beforeAdvice() {
System.out.println("beforeAdvice...");
}
@After("test()")
public void afterAdvice() {
System.out.println("afterAdvice...");
}
@Around("test()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("before");
try {
proceedingJoinPoint.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("after");
}
}
運(yùn)行結(jié)果

案例場(chǎng)景
這里我們通過(guò)一個(gè)日志記錄場(chǎng)景來(lái)完整的使用Aop切面業(yè)務(wù)層只需關(guān)心代碼邏輯實(shí)現(xiàn)而不用關(guān)心請(qǐng)求參數(shù)和響應(yīng)參數(shù)的日志記錄
那么首先我們需要自定義一個(gè)全局日志記錄的切面類GlobalLogAspect
然后在該類添加@Aspect注解,然后在定義一個(gè)公共的切入點(diǎn)(Pointcut),指向需要處理的包,然后在定義一個(gè)前置通知(添加@Before注解),后置通知(添加@AfterReturning)和環(huán)繞通知(添加@Around)方法實(shí)現(xiàn)即可
日志信息類
package cn.soboys.core;
import lombok.Data;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 18:48
* 日志信息
*/
@Data
public class LogSubject {
/**
* 操作描述
*/
private String description;
/**
* 操作用戶
*/
private String username;
/**
* 操作時(shí)間
*/
private String startTime;
/**
* 消耗時(shí)間
*/
private String spendTime;
/**
* URL
*/
private String url;
/**
* 請(qǐng)求類型
*/
private String method;
/**
* IP地址
*/
private String ip;
/**
* 請(qǐng)求參數(shù)
*/
private Object parameter;
/**
* 請(qǐng)求返回的結(jié)果
*/
private Object result;
/**
* 城市
*/
private String city;
/**
* 請(qǐng)求設(shè)備信息
*/
private String device;
}
全局日志攔截
package cn.soboys.core;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 14:52
* 切面
*/
public class BaseAspectSupport {
public Method resolveMethod(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature)point.getSignature();
Class<?> targetClass = point.getTarget().getClass();
Method method = getDeclaredMethod(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("無(wú)法解析目標(biāo)方法: " + signature.getMethod().getName());
}
return method;
}
private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethod(superClass, name, parameterTypes);
}
}
return null;
}
}
GlobalLogAspect類
package cn.soboys.core;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 15:22
* 全局日志記錄器
*/
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
/**
* 定義切面Pointcut
*/
@Pointcut("execution(public * cn.soboys.mallapi.controller.*.*(..))")
public void log() {
}
/**
* 環(huán)繞通知
*
* @param joinPoint
* @return
*/
@Around("log()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
LogSubject logSubject = new LogSubject();
//記錄時(shí)間定時(shí)器
TimeInterval timer = DateUtil.timer(true);
//執(zhí)行結(jié)果
Object result = joinPoint.proceed();
logSubject.setResult(result);
//執(zhí)行消耗時(shí)間
String endTime = timer.intervalPretty();
logSubject.setSpendTime(endTime);
//執(zhí)行參數(shù)
Method method = resolveMethod(joinPoint);
logSubject.setParameter(getParameter(method, joinPoint.getArgs()));
HttpServletRequest request = HttpContextUtil.getRequest();
// 接口請(qǐng)求時(shí)間
logSubject.setStartTime(DateUtil.now());
//請(qǐng)求鏈接
logSubject.setUrl(request.getRequestURL().toString());
//請(qǐng)求方法GET,POST等
logSubject.setMethod(request.getMethod());
//請(qǐng)求設(shè)備信息
logSubject.setDevice(HttpContextUtil.getDevice());
//請(qǐng)求地址
logSubject.setIp(HttpContextUtil.getIpAddr());
//接口描述
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
logSubject.setDescription(apiOperation.value());
}
String a = JSONUtil.toJsonPrettyStr(logSubject);
log.info(a);
return result;
}
/**
* 根據(jù)方法和傳入的參數(shù)獲取請(qǐng)求參數(shù)
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < parameters.length; i++) {
//將RequestBody注解修飾的參數(shù)作為請(qǐng)求參數(shù)
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
//將RequestParam注解修飾的參數(shù)作為請(qǐng)求參數(shù)
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
String key = parameters[i].getName();
if (requestBody != null) {
argList.add(args[i]);
} else if (requestParam != null) {
map.put(key, args[i]);
} else {
map.put(key, args[i]);
}
}
if (map.size() > 0) {
argList.add(map);
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}
到此這篇關(guān)于SpringBoot Aop 詳解和多種使用場(chǎng)景的文章就介紹到這了,更多相關(guān)SpringBoot Aop使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)隨機(jī)生成驗(yàn)證碼圖片
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)隨機(jī)生成驗(yàn)證碼圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
javaBean的基礎(chǔ)知識(shí)及常見(jiàn)亂碼解決方法
這篇文章主要介紹了javaBean的基礎(chǔ)知識(shí)及常見(jiàn)亂碼解決方法的相關(guān)資料,需要的朋友可以參考下2017-03-03
詳解Java數(shù)據(jù)庫(kù)連接JDBC基礎(chǔ)知識(shí)(操作數(shù)據(jù)庫(kù):增刪改查)
這篇文章主要介紹了詳解Java數(shù)據(jù)庫(kù)連接JDBC基礎(chǔ)知識(shí)(操作數(shù)據(jù)庫(kù):增刪改查),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
java讀取圖片并轉(zhuǎn)化為二進(jìn)制字符串的實(shí)現(xiàn)方法
這篇文章主要介紹了java讀取圖片并轉(zhuǎn)化為二進(jìn)制字符串的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-09-09
Java使用FutureTask實(shí)現(xiàn)預(yù)加載的示例詳解
基于FutureTask的特性,通??梢允褂肍utureTask做一些預(yù)加載工作,比如一些時(shí)間較長(zhǎng)的計(jì)算等,本文就來(lái)和大家講講具體實(shí)現(xiàn)方法吧,感興趣的可以了解一下2023-06-06
IDEA Error:java: 無(wú)效的源發(fā)行版: 17錯(cuò)誤
本文主要介紹了IDEA Error:java: 無(wú)效的源發(fā)行版: 17錯(cuò)誤,這個(gè)錯(cuò)誤是因?yàn)槟腎DEA編譯器不支持Java 17版本,您需要更新您的IDEA編譯器或者將您的Java版本降級(jí)到IDEA支持的版本,本文就來(lái)詳細(xì)的介紹一下2023-08-08
java swing實(shí)現(xiàn)的掃雷游戲及改進(jìn)版完整示例
這篇文章主要介紹了java swing實(shí)現(xiàn)的掃雷游戲及改進(jìn)版,結(jié)合完整實(shí)例形式對(duì)比分析了java使用swing框架實(shí)現(xiàn)掃雷游戲功能與相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
springboot項(xiàng)目中后端接收前端傳參的方法示例詳解
這篇文章主要介紹了springboot項(xiàng)目中一些后端接收前端傳參的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06
Java由淺入深細(xì)數(shù)數(shù)組的操作上
數(shù)組對(duì)于每一門編程語(yǔ)言來(lái)說(shuō)都是重要的數(shù)據(jù)結(jié)構(gòu)之一,當(dāng)然不同語(yǔ)言對(duì)數(shù)組的實(shí)現(xiàn)及處理也不盡相同。Java?語(yǔ)言中提供的數(shù)組是用來(lái)存儲(chǔ)固定大小的同類型元素2022-04-04

