SpringBoot實(shí)現(xiàn)AOP日志切面功能的詳細(xì)教程
Spring Boot 實(shí)現(xiàn)AOP日志切面全流程教程
效果
切入com.anfioo下的所有controller層

切入com.anfioo下的所有service層

切入自定義注解@LogRecord


全都開(kāi)啟

可以使用aop切片更好的打印這個(gè)方法信息,而不污染原有的方法
一、引入依賴
首先,確保你的Spring Boot項(xiàng)目已經(jīng)引入了AOP相關(guān)依賴。Spring Boot Starter通常已經(jīng)包含了AOP依賴,但你可以在pom.xml中顯式添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、配置AOP屬性(可選)
為了靈活控制切面的開(kāi)啟與關(guān)閉,我們可以通過(guò)配置文件添加自定義屬性。例如:
# application.yaml
aop:
debug:
controller: true # 控制Controller切面是否開(kāi)啟
service: false # 控制Service切面是否開(kāi)啟
log-record: true # 控制LogRecord注解切面是否開(kāi)啟
并通過(guò)@ConfigurationProperties將其綁定到Java類:
@Data
@Component
@ConfigurationProperties(prefix = "aop.debug")
public class AopDebugProperties {
private Boolean controller = true;
private Boolean service = false;
private Boolean logRecord = true;
}
三、定義切點(diǎn)(Pointcut)
切點(diǎn)用于指定哪些類或方法會(huì)被AOP攔截。常見(jiàn)的切點(diǎn)表達(dá)式有:
execution(public * com.example..controller..*(..)):攔截所有controller包下的公共方法@annotation(com.example.LogRecord):攔截所有被自定義注解標(biāo)記的方法
四、實(shí)現(xiàn)切面(Aspect)
以Controller層日志為例,實(shí)現(xiàn)一個(gè)切面類:
package com.anfioo.common.log;
import com.anfioo.common.bean.AopDebugProperties;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 全局 Controller 層日志記錄切面
*/
@Aspect
@Component
@Slf4j
public class ControllerLogAspect {
@Autowired
private AopDebugProperties aopDebugProperties;
/**
* 定義切點(diǎn),匹配 com.anfioo 包下所有子包中的 controller 類的公共方法
*/
@Pointcut("execution(public * com.anfioo..controller..*(..))")
public void controllerMethods() {
}
/**
* 環(huán)繞通知,用于記錄 controller 層方法的請(qǐng)求和響應(yīng)信息
*
* @param joinPoint 切入點(diǎn)對(duì)象,包含被攔截方法的信息
* @return 被攔截方法的執(zhí)行結(jié)果
* @throws Throwable 如果執(zhí)行過(guò)程中出現(xiàn)異常
*/
@Around("controllerMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
if (Boolean.FALSE.equals(aopDebugProperties.getController())) {
// 不開(kāi)啟,直接執(zhí)行原方法
return joinPoint.proceed();
}
// 記錄開(kāi)始時(shí)間
long start = System.currentTimeMillis();
// 獲取目標(biāo)類的 Class 對(duì)象
Class<?> targetClass = joinPoint.getTarget().getClass();
// 獲取目標(biāo)類的 Logger 對(duì)象
Logger logger = LoggerFactory.getLogger(targetClass);
// 獲取當(dāng)前請(qǐng)求的 HttpServletRequest 對(duì)象
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 提取請(qǐng)求的 URL、方法、IP 地址、處理方法和參數(shù)
String url = request.getRequestURL().toString();
String method = request.getMethod();
String ip = request.getRemoteAddr();
String classMethod = joinPoint.getSignature().toShortString();
Object[] args = joinPoint.getArgs();
String red = "\u001B[31m"; // ANSI 紅色
String line = IntStream.range(0, 200) // 200 可以改成任意長(zhǎng)度
.mapToObj(i -> "#")
.collect(Collectors.joining());
System.out.println(red + line);
// 記錄請(qǐng)求信息
logger.info("\n====== ?? 請(qǐng)求信息 ======\n" +
"?? URL : {}\n" +
"?? Method : {}\n" +
"?? IP : {}\n" +
"?? Handler : {}\n" +
"?? Parameters : {}\n" +
"==========================",
url, method, ip, classMethod, Arrays.toString(args));
// 嘗試執(zhí)行目標(biāo)方法,并記錄執(zhí)行結(jié)果或異常信息
Object result;
try {
result = joinPoint.proceed();
} catch (Exception e) {
// 記錄異常信息,并重新拋出異常
logger.error("\n====== ?? ? 異常信息 ======\n" +
"?? Handler : {}\n" +
"?? Error : {}\n" +
"==========================",
classMethod, e.getMessage(), e);
throw e;
}
// 記錄結(jié)束時(shí)間,并計(jì)算耗時(shí)
long end = System.currentTimeMillis();
// 記錄響應(yīng)信息
logger.info("\n====== ?? ? 響應(yīng)信息 ======\n" +
"?? Handler : {}\n" +
"?? Response : {}\n" +
"?? 耗時(shí) : {} ms\n" +
"==========================",
classMethod, result, end - start);
return result;
}
}
說(shuō)明
@Aspect:聲明該類為切面@Pointcut:定義切點(diǎn)@Around:環(huán)繞通知,可在方法執(zhí)行前后插入邏輯ProceedingJoinPoint:用于獲取方法信息、參數(shù)、執(zhí)行目標(biāo)方法等
五、自定義注解與切面
有時(shí)我們希望只對(duì)特定方法記錄日志,可以自定義注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRecord {
String value() default "";
}
并實(shí)現(xiàn)對(duì)應(yīng)的切面:
package com.anfioo.common.log;
import com.anfioo.common.bean.AopDebugProperties;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 記錄 @LogRecord 注解標(biāo)記的方法調(diào)用日志
*/
@Aspect
@Component
@Slf4j
public class LogRecordAspect {
@Autowired
private AopDebugProperties aopDebugProperties;
/**
* 切點(diǎn):攔截所有帶有 @LogRecord 注解的方法
*/
@Pointcut("@annotation(com.anfioo.common.log.LogRecord)")
public void logRecordMethods() {
}
/**
* 環(huán)繞通知:記錄方法執(zhí)行詳情
*/
@Around("logRecordMethods() && @annotation(logRecord)")
public Object around(ProceedingJoinPoint joinPoint, LogRecord logRecord) throws Throwable {
if (Boolean.FALSE.equals(aopDebugProperties.getLogRecord())) {
return joinPoint.proceed(); // 不開(kāi)啟則跳過(guò)
}
long start = System.currentTimeMillis();
Class<?> targetClass = joinPoint.getTarget().getClass();
Logger logger = LoggerFactory.getLogger(targetClass);
String description = logRecord.value(); // 注解中的描述信息
String methodName = joinPoint.getSignature().toShortString();
Object[] args = joinPoint.getArgs();
logger.info("\n====== ?? LogRecord 方法調(diào)用 ======\n" +
"?? 描述 : {}\n" +
"?? 方法 : {}\n" +
"?? 參數(shù) : {}\n" +
"=====================================",
description, methodName, Arrays.toString(args));
Object result;
try {
result = joinPoint.proceed();
} catch (Exception e) {
logger.error("\n====== ??? LogRecord 異常 ======\n" +
"?? 方法 : {}\n" +
"?? 異常信息 : {}\n" +
"=====================================",
methodName, e.getMessage(), e);
throw e;
}
long end = System.currentTimeMillis();
logger.info("\n====== ??? LogRecord 返回結(jié)果 ======\n" +
"?? 方法 : {}\n" +
"?? 返回值 : {}\n" +
"?? 耗時(shí) : {} ms\n" +
"=====================================",
methodName, result, end - start);
return result;
}
}
六、Service層切面實(shí)現(xiàn)
同理,可以為Service層實(shí)現(xiàn)切面:
package com.anfioo.common.log;
import com.anfioo.common.bean.AopDebugProperties;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* Service 層方法日志切面(記錄業(yè)務(wù)邏輯調(diào)用)
*/
@Aspect
@Component
@Slf4j
public class ServiceLogAspect {
@Autowired
private AopDebugProperties aopDebugProperties;
/**
* 切入 com.anfioo 包下所有 service 的方法
*/
@Pointcut("execution(* com.anfioo..service..*(..))")
public void serviceMethods() {
}
/**
* 環(huán)繞通知記錄 service 層方法的調(diào)用情況
*/
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
if (Boolean.FALSE.equals(aopDebugProperties.getService())) {
// 不開(kāi)啟,直接執(zhí)行原方法
return joinPoint.proceed();
}
long start = System.currentTimeMillis();
Class<?> targetClass = joinPoint.getTarget().getClass();
Logger logger = LoggerFactory.getLogger(targetClass);
String methodName = joinPoint.getSignature().toShortString();
Object[] args = joinPoint.getArgs();
logger.info("\n====== ?? Service 方法調(diào)用 ======\n" +
"?? Method : {}\n" +
"?? Parameters : {}\n" +
"===============================",
methodName, Arrays.toString(args));
Object result;
try {
result = joinPoint.proceed();
} catch (Exception e) {
logger.error("\n====== ?? ? Service 異常 ======\n" +
"?? Method : {}\n" +
"?? Error : {}\n" +
"=============================",
methodName, e.getMessage(), e);
throw e;
}
long end = System.currentTimeMillis();
logger.info("\n====== ?? ? Service 返回結(jié)果 ======\n" +
"?? Method : {}\n" +
"?? Result : {}\n" +
"?? 耗時(shí) : {} ms\n" +
"===============================",
methodName, result, end - start);
return result;
}
}
七、完整流程總結(jié)
- 引入依賴:確保
spring-boot-starter-aop已添加 - 配置屬性:通過(guò)配置文件靈活控制切面開(kāi)關(guān)
- 定義切點(diǎn):用表達(dá)式或注解指定攔截范圍
- 實(shí)現(xiàn)切面:用
@Aspect、@Around等實(shí)現(xiàn)日志邏輯 - 自定義注解:實(shí)現(xiàn)更細(xì)粒度的日志控制
- 應(yīng)用注解:在需要記錄日志的方法上加上自定義注解
八、常見(jiàn)問(wèn)題
- 切面不生效? 檢查
@Component、@Aspect是否加上,切點(diǎn)表達(dá)式是否正確,AOP依賴是否引入。 - 日志打印不全? 檢查日志級(jí)別、切面邏輯是否被條件限制跳過(guò)。
AOP 的主要好處
關(guān)注點(diǎn)分離(Separation of Concerns)
AOP 允許你將與業(yè)務(wù)邏輯無(wú)關(guān)的“橫切關(guān)注點(diǎn)”(如日志記錄、安全控制、異常處理、事務(wù)管理)從核心業(yè)務(wù)代碼中分離出去,使業(yè)務(wù)邏輯更專注、更清晰。
避免重復(fù)代碼,提高可維護(hù)性
常見(jiàn)的重復(fù)操作如打印日志、性能統(tǒng)計(jì)、權(quán)限校驗(yàn),如果分散在各個(gè)方法中,會(huì)導(dǎo)致維護(hù)困難。AOP 將這些重復(fù)邏輯集中在一個(gè)地方,修改一次即可生效全局。
不侵入業(yè)務(wù)代碼
通過(guò) AOP,你可以在不修改原方法的前提下,增強(qiáng)其功能(如記錄參數(shù)、返回值、異常信息等),實(shí)現(xiàn)“開(kāi)閉原則”:對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉。
增強(qiáng)系統(tǒng)的可觀測(cè)性與調(diào)試能力
配合 AOP 自動(dòng)記錄方法調(diào)用軌跡、執(zhí)行耗時(shí)、輸入輸出信息,極大提升問(wèn)題排查和性能分析的效率。
靈活可配置
結(jié)合注解、自定義屬性、開(kāi)關(guān)控制(如 AopDebugProperties),你可以根據(jù)環(huán)境或條件動(dòng)態(tài)啟用/禁用某些切面邏輯,適應(yīng)多種部署或調(diào)試場(chǎng)景。
提高開(kāi)發(fā)效率
開(kāi)發(fā)人員無(wú)需手動(dòng)添加日志、異常處理等模板式代碼,只需專注于業(yè)務(wù)邏輯,其余交由統(tǒng)一切面處理,顯著提高開(kāi)發(fā)效率和代碼一致性。
總結(jié)
AOP 幫你在“不碰業(yè)務(wù)代碼”的前提下,實(shí)現(xiàn)系統(tǒng)級(jí)增強(qiáng),讓代碼更干凈、功能更強(qiáng)大、維護(hù)更輕松。
通過(guò)AOP切面,你可以優(yōu)雅地實(shí)現(xiàn)日志記錄、權(quán)限校驗(yàn)等橫切關(guān)注點(diǎn),極大提升代碼的可維護(hù)性和可擴(kuò)展性。希望本文能幫助你快速上手Spring Boot的AOP切面開(kāi)發(fā)!
以上就是SpringBoot實(shí)現(xiàn)AOP日志切面功能的詳細(xì)教程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot AOP日志切面的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java語(yǔ)言實(shí)現(xiàn)非遞歸實(shí)現(xiàn)樹(shù)的前中后序遍歷總結(jié)
今天小編就為大家分享一篇關(guān)于Java語(yǔ)言實(shí)現(xiàn)非遞歸實(shí)現(xiàn)樹(shù)的前中后序遍歷總結(jié),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
對(duì)SpringBoot項(xiàng)目Jar包進(jìn)行加密防止反編譯的方案
最近項(xiàng)目要求部署到其他公司的服務(wù)器上,但是又不想將源碼泄露出去,要求對(duì)正式環(huán)境的啟動(dòng)包進(jìn)行安全性處理,防止客戶直接通過(guò)反編譯工具將代碼反編譯出來(lái),本文介紹了如何對(duì)SpringBoot項(xiàng)目Jar包進(jìn)行加密防止反編譯,需要的朋友可以參考下2024-08-08
springboot中的controller參數(shù)映射問(wèn)題小結(jié)
這篇文章主要介紹了springboot中的controller參數(shù)映射問(wèn)題小結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-12-12
Spring生命周期回調(diào)與容器擴(kuò)展詳解
這篇文章主要介紹了Spring生命周期回調(diào)與容器擴(kuò)展詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12

