SpringBoot統(tǒng)計(jì)接口調(diào)用耗時(shí)的三種方式
引言
在實(shí)際開發(fā)中,了解項(xiàng)目中接口的響應(yīng)時(shí)間是必不可少的事情。SpringBoot 項(xiàng)目支持監(jiān)聽接口的功能也不止一個(gè),接下來我們分別以 AOP、ApplicationListener、Tomcat 三個(gè)方面去實(shí)現(xiàn)三種不同的監(jiān)聽接口響應(yīng)時(shí)間的操作。
AOP
首先我們?cè)陧?xiàng)目中創(chuàng)建一個(gè)類 ,比如就叫 WebLogAspect ,然后在該類上加上 @Aspect 和 @Component 注解,聲明是一個(gè) Bean 并且是一個(gè)切面:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
// 定義一個(gè)切入點(diǎn),攔截所有帶有@RequestMapping注解的方法
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void webLog() {}
// 前置通知,在方法執(zhí)行前記錄請(qǐng)求信息
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄請(qǐng)求信息
logger.info("請(qǐng)求開始:URL={}, IP={}, 方法={}", request.getRequestURL(), request.getRemoteAddr(), request.getMethod());
}
// 環(huán)繞通知,記錄方法執(zhí)行時(shí)間
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 繼續(xù)執(zhí)行被攔截的方法
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
// 記錄執(zhí)行時(shí)間
logger.info("請(qǐng)求結(jié)束:耗時(shí)={}ms", executeTime);
return result;
}
// 異常通知,在方法拋出異常時(shí)記錄異常信息
@AfterThrowing(pointcut = "webLog()", throwing = "ex")
public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄異常信息
logger.error("請(qǐng)求異常:URL={}, 異常={}", request.getRequestURL(), ex.getMessage());
}
// 后置通知(返回通知),在方法正常返回后記錄信息
@AfterReturning(returning = "retVal", pointcut = "webLog()")
public void doAfterReturning(JoinPoint joinPoint, Object retVal) {
// 你可以在這里記錄返回值,但通常我們不記錄,因?yàn)榭赡軙?huì)包含敏感信息
// logger.info("請(qǐng)求返回:返回值={}", retVal);
}
}
2024-06-19 17:49:37.373 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求開始:URL=http://localhost:18080/springboot/test1, IP=0:0:0:0:0:0:0:1, 方法=POST
2024-06-19 17:49:37.386 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求結(jié)束:耗時(shí)=13ms
2024-06-19 17:49:37.501 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求開始:URL=http://localhost:18080/springboot/test2, IP=0:0:0:0:0:0:0:1, 方法=POST
2024-06-19 17:49:37.516 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求結(jié)束:耗時(shí)=15ms
2024-06-19 17:49:37.905 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求開始:URL=http://localhost:18080/springboot/test3, IP=0:0:0:0:0:0:0:1, 方法=POST
2024-06-19 17:49:37.913 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求結(jié)束:耗時(shí)=8ms
優(yōu)點(diǎn):
- 全局性:可以在不修改業(yè)務(wù)代碼的情況下,對(duì)全局范圍內(nèi)的接口進(jìn)行執(zhí)行時(shí)間的記錄。
- 靈活性:可以根據(jù)需要靈活定義哪些接口需要記錄執(zhí)行時(shí)間。
- 精確性:可以精確記錄從方法開始執(zhí)行到結(jié)束的時(shí)間。
缺點(diǎn):
- 配置復(fù)雜性:AOP配置可能相對(duì)復(fù)雜,特別是對(duì)于初學(xué)者來說。
- 性能開銷:雖然性能開銷通常很小,但在高并發(fā)場(chǎng)景下仍然需要考慮,并且它是會(huì)阻塞主線程的。
**常用性:**在Spring框架中,AOP是一個(gè)強(qiáng)大的工具,用于實(shí)現(xiàn)諸如日志記錄、事務(wù)管理等橫切關(guān)注點(diǎn)。因此,使用AOP記錄接口執(zhí)行時(shí)間是一種非常常見和推薦的做法。
ApplicationListener
首先我們?cè)陧?xiàng)目中創(chuàng)建一個(gè)類 ,比如就叫 TakeTimeCountListener,然后實(shí)現(xiàn) ApplicationListener 接口:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.ServletRequestHandledEvent;
@Component
public class TakeTimeCountListener implements ApplicationListener<ServletRequestHandledEvent> {
public final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
Throwable failureCause = event.getFailureCause() ;
if (failureCause != null) {
logger.warn("錯(cuò)誤原因: {}", failureCause.getMessage());
}
// 比如我這里只記錄接口響應(yīng)時(shí)間大于1秒的日志
if (event.getProcessingTimeMillis() > 1000) {
logger.warn("請(qǐng)求客戶端地址:{}, 請(qǐng)求URL: {}, 請(qǐng)求Method: {}, 請(qǐng)求耗時(shí):{} ms",
event.getClientAddress(),
event.getRequestUrl(),
event.getMethod(),
event.getProcessingTimeMillis());
}
}
}
2024-06-19 17:14:59.620 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求客戶端地址:0:0:0:0:0:0:0:1, 請(qǐng)求URL: /springboot/test1, 請(qǐng)求Method: GET, 請(qǐng)求耗時(shí):51 ms
2024-06-19 17:14:59.716 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求客戶端地址:0:0:0:0:0:0:0:1, 請(qǐng)求URL: /springboot/test2, 請(qǐng)求Method: GET, 請(qǐng)求耗時(shí):136 ms
2024-06-19 17:14:59.787 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求客戶端地址:0:0:0:0:0:0:0:1, 請(qǐng)求URL: /springboot/test3, 請(qǐng)求Method: POST, 請(qǐng)求耗時(shí):255 ms
2024-06-19 17:14:59.859 [TID: N/A] WARN [com.springboot.demo.TakeTimeCountListener] 請(qǐng)求客戶端地址:0:0:0:0:0:0:0:1, 請(qǐng)求URL: /springboot/test4, 請(qǐng)求Method: POST, 請(qǐng)求耗時(shí):167 ms
優(yōu)點(diǎn):
- 集成性:與Spring MVC框架緊密集成,無需額外配置。
- 性能:改方法是不會(huì)阻塞主線程的,也就是說 該方法在處理的時(shí)候,controller 已經(jīng)正常返回了,可以通過在該方法進(jìn)行斷點(diǎn)調(diào)試來驗(yàn)證。
- 簡(jiǎn)單易用:實(shí)現(xiàn)ApplicationListener接口并監(jiān)聽ServletRequestHandledEvent事件即可。
缺點(diǎn):
- 適用范圍:主要適用于Spring MVC 框架下的 Web 請(qǐng)求,對(duì)于非 Web 接口(如RESTful API)可能不適用。
- 精度:只能記錄整個(gè)請(qǐng)求的處理時(shí)間,無法精確到具體的方法執(zhí)行時(shí)間。
**常用性:**在Spring MVC應(yīng)用中,使用ApplicationListener來記錄請(qǐng)求處理時(shí)間是一種常見做法,但通常用于監(jiān)控和性能分析,而不是精確記錄接口執(zhí)行時(shí)間。
Tomcat
Tomcat 的實(shí)現(xiàn)很簡(jiǎn)單,只需要開啟它本身就支持的訪問日志就可以了 ,在 SpringBoot 中,我們可以在 properties 或 yaml 文件中增加下面配置:
# 啟用Tomcat訪問日志
server.tomcat.accesslog.enabled=true
# 啟用緩沖模式,日志會(huì)先寫入緩沖區(qū),然后定期刷新到磁盤
server.tomcat.accesslog.buffered=true
# 指定日志存儲(chǔ)目錄,這里是相對(duì)于項(xiàng)目根目錄的logs文件夾
server.tomcat.accesslog.directory=logs
# 定義日志文件名的日期格式
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd
# 定義日志記錄的格式
# 各個(gè)字段的意義:
# %{X-Forwarded-For}i: 請(qǐng)求頭中的X-Forwarded-For,通常用于記錄客戶端真實(shí)IP
# %p: 本地端口
# %l: 遠(yuǎn)程用戶,通常為'-'
# %r: 請(qǐng)求的第一行(例如:GET / HTTP/1.1)
# %t: 請(qǐng)求時(shí)間(格式由日志處理器決定)
# 注意:這里有一個(gè)重復(fù)的%r,可能是個(gè)錯(cuò)誤,通常第二個(gè)%r不需要
# %s: HTTP狀態(tài)碼
# %b: 響應(yīng)字節(jié)數(shù),不包括HTTP頭,如果為0則不輸出
# %T: 請(qǐng)求處理時(shí)間(以秒為單位)
server.tomcat.accesslog.pattern=%{X-Forwarded-For}i %p %l %r %t %r %s %b %T
# 日志文件名前綴
server.tomcat.accesslog.prefix=localhost_access_log
# 日志文件名后綴
server.tomcat.accesslog.suffix=.log
server:
tomcat:
accesslog:
enabled: true # 啟用Tomcat訪問日志
buffered: true # 啟用緩沖模式,日志會(huì)先寫入緩沖區(qū),然后定期刷新到磁盤
directory: logs # 指定日志存儲(chǔ)目錄,這里是相對(duì)于項(xiàng)目根目錄的logs文件夾
file-date-format: ".yyyy-MM-dd" # 定義日志文件名的日期格式
pattern: "%{X-Forwarded-For}i %p %l %r %t %s %b %T" # 定義日志記錄的格式
prefix: localhost_access_log # 日志文件名前綴
suffix: .log # 日志文件名后綴
- 8080 - - [19/Jun/2024:00:00:09 +0800] GET /springboot/test1 HTTP/1.1 200 92 0.247 Ignored_Trace
- 8080 - - [19/Jun/2024:00:00:09 +0800] GET /springboot/test2 HTTP/1.1 200 92 0.247 Ignored_Trace
- 8080 - - [19/Jun/2024:09:49:55 +0800] POST /springboot/test3 HTTP/1.1 200 291556 0.314 Ignored_Trace
優(yōu)點(diǎn):
- 集成性:Tomcat 內(nèi)置功能,無需額外代碼或配置。
- 全面性:記錄所有通過 Tomcat 處理的請(qǐng)求和響應(yīng)信息。
缺點(diǎn):
- 性能:訪問日志可能會(huì)對(duì) Tomcat 性能產(chǎn)生一定影響。
- 精度:同樣只能記錄整個(gè)請(qǐng)求的處理時(shí)間,無法精確到具體的方法執(zhí)行時(shí)間。
- 配置復(fù)雜性:對(duì)于復(fù)雜的日志格式或需求,可能需要修改 Tomcat 的配置文件。
**常用性:**Tomcat 的訪問日志通常用于監(jiān)控 Web 服務(wù)器的訪問情況,如 IP 地址、請(qǐng)求路徑、HTTP 狀態(tài)碼等。雖然它可以記錄請(qǐng)求處理時(shí)間,但通常不會(huì)用于精確的性能分析或接口執(zhí)行時(shí)間記錄。
以上就是SpringBoot統(tǒng)計(jì)接口調(diào)用耗時(shí)的三種方式的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot統(tǒng)計(jì)接口耗時(shí)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot中統(tǒng)計(jì)方法耗時(shí)的七種實(shí)現(xiàn)方式小結(jié)
- SpringBoot統(tǒng)計(jì)接口請(qǐng)求耗時(shí)的方法詳解
- Springboot之如何統(tǒng)計(jì)代碼執(zhí)行耗時(shí)時(shí)間
- Spring?Boot源碼實(shí)現(xiàn)StopWatch優(yōu)雅統(tǒng)計(jì)耗時(shí)
- springboot基于過濾器實(shí)現(xiàn)接口請(qǐng)求耗時(shí)統(tǒng)計(jì)操作
- SpringBoot中的7種耗時(shí)統(tǒng)計(jì)的實(shí)現(xiàn)方法與應(yīng)用場(chǎng)景
相關(guān)文章
java stream中Collectors的用法實(shí)例精講
這篇文章主要為大家介紹了java stream中Collectors的用法實(shí)例精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Java實(shí)現(xiàn)Excel圖片URL篩選與大小檢測(cè)的全過程
在數(shù)據(jù)處理場(chǎng)景中,我們常需篩選Excel中的圖片URL,本文分享一個(gè)完整的Java方案,涵蓋從讀取圖片URL到檢測(cè)有效性、篩選大小,再到生成新Excel文件的全過程,同時(shí)講解開發(fā)與優(yōu)化過程,需要的朋友可以參考下2025-08-08
基于Java實(shí)現(xiàn)修改圖片分辨率示例代碼
這篇文章主要介紹了一個(gè)可以修改圖片分辨率的java工具類,文中的示例代碼講解詳細(xì),對(duì)學(xué)習(xí)JAVA有一定的幫助,感興趣的小伙伴快來跟隨小編一起學(xué)習(xí)吧2021-12-12
Spring與Mybatis整合方式(mybatis-spring整合jar包功能)
這篇文章主要介紹了Spring與Mybatis整合方式(mybatis-spring整合jar包功能),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
Hibernate框架數(shù)據(jù)分頁(yè)技術(shù)實(shí)例分析
這篇文章主要介紹了Hibernate框架數(shù)據(jù)分頁(yè)技術(shù),結(jié)合實(shí)例形式分析了Hibernate框架實(shí)現(xiàn)數(shù)據(jù)分頁(yè)的原理,步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-03-03

