SpringBoot使用AOP切面對請求進(jìn)行日志記錄方式
簡介
AOP(Aspect-Oriented Programming,面向切面編程)是一種編程范式,旨在通過分離橫切關(guān)注點(cross-cutting concerns)來提高代碼的模塊化。
橫切關(guān)注點是指那些跨越多個模塊的功能,例如日志記錄、事務(wù)管理、安全性檢查等。
AOP 通過將這些功能從核心業(yè)務(wù)邏輯中分離出來,使代碼更易于維護(hù)和擴(kuò)展。
核心概念
1、切面(Aspect):
- 切面是橫切關(guān)注點的模塊化實現(xiàn)。它包含通知(Advice)和切點(Pointcut)。
- 例如,日志記錄功能可以作為一個切面。
2、連接點(Join Point):
- 連接點是程序執(zhí)行過程中的特定點,例如方法調(diào)用、異常拋出等。
- AOP 可以在這些點上插入額外的行為。
3、通知(Advice):
- 通知是切面在特定連接點執(zhí)行的動作。常見的通知類型包括:
- 前置通知(Before Advice):在目標(biāo)方法執(zhí)行之前執(zhí)行。
- 后置通知(After Advice):在目標(biāo)方法執(zhí)行之后執(zhí)行。
- 返回通知(After Returning Advice):在目標(biāo)方法成功返回后執(zhí)行。
- 異常通知(After Throwing Advice):在目標(biāo)方法拋出異常后執(zhí)行。
- 環(huán)繞通知(Around Advice):在目標(biāo)方法執(zhí)行前后都執(zhí)行,可以控制是否執(zhí)行目標(biāo)方法。
4、切點(Pointcut):
- 切點用于定義哪些連接點會觸發(fā)通知。它通過表達(dá)式或規(guī)則匹配連接點。
- 例如,匹配所有以 save 開頭的方法。
5、引入(Introduction):
- 引入允許向現(xiàn)有類添加新的方法或?qū)傩?,而無需修改類的源代碼。
6、目標(biāo)對象(Target Object):
目標(biāo)對象是被一個或多個切面通知的對象,通常是核心業(yè)務(wù)邏輯的類。
7、代理(Proxy):
- AOP 框架通過創(chuàng)建代理對象來實現(xiàn)切面功能。
- 代理對象在目標(biāo)對象的方法調(diào)用前后插入通知。
AOP 的實現(xiàn)方式
1、靜態(tài)代理:
在編譯時生成代理類,例如 AspectJ 的編譯時織入。
2、動態(tài)代理:
在運行時生成代理類,例如 Spring AOP 使用的 JDK 動態(tài)代理或 CGLIB。
AOP 的應(yīng)用場景
1、日志記錄:
在方法調(diào)用前后記錄日志,而不需要在每個方法中手動添加日志代碼。
2、事務(wù)管理:
在方法執(zhí)行前后管理事務(wù)的開啟、提交或回滾。
3、安全性檢查:
在方法調(diào)用前檢查用戶權(quán)限。
4、性能監(jiān)控:
在方法執(zhí)行前后記錄時間,用于性能分析。
5、異常處理:
在方法拋出異常時執(zhí)行特定的處理邏輯。
AOP 的優(yōu)點
1、模塊化:
將橫切關(guān)注點從核心業(yè)務(wù)邏輯中分離出來,使代碼更清晰、更易于維護(hù)。
2、代碼復(fù)用:
通過切面實現(xiàn)通用功能,避免重復(fù)代碼。
3、靈活性:
可以動態(tài)地添加或移除切面,而不需要修改核心業(yè)務(wù)代碼。
AOP 的缺點
1、學(xué)習(xí)曲線:
對于初學(xué)者來說,AOP 的概念和實現(xiàn)方式可能較難理解。
2、調(diào)試?yán)щy:
由于切面邏輯是動態(tài)插入的,調(diào)試時可能難以追蹤問題。
3、性能開銷:
動態(tài)代理和運行時織入可能帶來一定的性能開銷。
AOP 的實現(xiàn)框架
1、Spring AOP:
Spring 框架提供的 AOP 實現(xiàn),基于動態(tài)代理,易于與 Spring 集成。
2、AspectJ:
功能強(qiáng)大的 AOP 框架,支持編譯時織入和運行時織入。
3、JBoss AOP:
JBoss 提供的 AOP 實現(xiàn),支持復(fù)雜的切面邏輯。
代碼實戰(zhàn)
- maven依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-demo-log-aop</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-demo-log-aop</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>com.xiaoma</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 解析 UserAgent 信息 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
</dependency>
</dependencies>
<build>
<finalName>spring-boot-demo-log-aop</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>- AopLog.java
@Aspect
@Component
@Slf4j
public class AopLog {
private static final String START_TIME = "request-start";
/**
* 切入點
*/
@Pointcut("execution(public * com.xiaoma.log.aop.controller.*Controller.*(..))")
public void log() {
}
/**
* 前置操作
*
* @param point 切入點
*/
@Before("log()")
public void beforeLog(JoinPoint point) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
log.info("【請求 URL】:{}", request.getRequestURL());
log.info("【請求 IP】:{}", request.getRemoteAddr());
log.info("【請求類名】:{},【請求方法名】:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());
Map<String, String[]> parameterMap = request.getParameterMap();
log.info("【請求參數(shù)】:{},", JSONUtil.toJsonStr(parameterMap));
Long start = System.currentTimeMillis();
request.setAttribute(START_TIME, start);
}
/**
* 環(huán)繞操作
*
* @param point 切入點
* @return 原方法返回值
* @throws Throwable 異常信息
*/
@Around("log()")
public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
Object result = point.proceed();
log.info("【返回值】:{}", JSONUtil.toJsonStr(result));
return result;
}
/**
* 后置操作
*/
@AfterReturning("log()")
public void afterReturning() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
Long start = (Long) request.getAttribute(START_TIME);
Long end = System.currentTimeMillis();
log.info("【請求耗時】:{}毫秒", end - start);
String header = request.getHeader("User-Agent");
UserAgent userAgent = UserAgent.parseUserAgentString(header);
log.info("【瀏覽器類型】:{},【操作系統(tǒng)】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
}
}- TestController.java
@RestController
public class TestController {
/**
* 測試方法
*
* @param who 測試參數(shù)
* @return {@link Dict}
*/
@GetMapping("/test")
public Dict test(String who) {
return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who);
}
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

