当前位置: 首页 > news >正文

SpringBoot 统计接口调用耗时的多种方式

在实际开发中,了解项目中接口的响应时间是必不可少的事情。SpringBoot 项目支持监听接口的功能也不止一个,接下来我们分别以 AOP、ApplicationListener、Tomcat 三个方面去实现三种不同的监听接口响应时间的操作。

AOP

首先我们在项目中创建一个类 ,比如就叫 WebLogAspect ,然后在该类上加上 @Aspect 和 @Component 注解,声明是一个 Bean 并且是一个切面:

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);  // 定义一个切入点,拦截所有带有@RequestMapping注解的方法@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")  public void webLog() {}  // 前置通知,在方法执行前记录请求信息  @Before("webLog()")  public void doBefore(JoinPoint joinPoint) {  ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();  HttpServletRequest request = attributes.getRequest();  // 记录请求信息  logger.info("请求开始:URL={}, IP={}, 方法={}", request.getRequestURL(), request.getRemoteAddr(), request.getMethod());  }  // 环绕通知,记录方法执行时间  @Around("webLog()")  public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {  long startTime = System.currentTimeMillis();  Object result = joinPoint.proceed(); // 继续执行被拦截的方法  long endTime = System.currentTimeMillis();  long executeTime = endTime - startTime;  // 记录执行时间  logger.info("请求结束:耗时={}ms", executeTime);  return result;  }  // 异常通知,在方法抛出异常时记录异常信息  @AfterThrowing(pointcut = "webLog()", throwing = "ex")  public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {  ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();  HttpServletRequest request = attributes.getRequest();  // 记录异常信息  logger.error("请求异常:URL={}, 异常={}", request.getRequestURL(), ex.getMessage());  }  // 后置通知(返回通知),在方法正常返回后记录信息  @AfterReturning(returning = "retVal", pointcut = "webLog()")  public void doAfterReturning(JoinPoint joinPoint, Object retVal) {  // 你可以在这里记录返回值,但通常我们不记录,因为可能会包含敏感信息  // logger.info("请求返回:返回值={}", retVal);  }  
}
2024-06-19 17:49:37.373 [TID: N/A] WARN  [com.springboot.demo.TakeTimeCountListener] 请求开始: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] 请求结束:耗时=13ms
2024-06-19 17:49:37.501 [TID: N/A] WARN  [com.springboot.demo.TakeTimeCountListener] 请求开始: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] 请求结束:耗时=15ms
2024-06-19 17:49:37.905 [TID: N/A] WARN  [com.springboot.demo.TakeTimeCountListener] 请求开始: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] 请求结束:耗时=8ms

优点:

  • 全局性:可以在不修改业务代码的情况下,对全局范围内的接口进行执行时间的记录。
  • 灵活性:可以根据需要灵活定义哪些接口需要记录执行时间。
  • 精确性:可以精确记录从方法开始执行到结束的时间。

缺点:

  • 配置复杂性:AOP配置可能相对复杂,特别是对于初学者来说。
  • 性能开销:虽然性能开销通常很小,但在高并发场景下仍然需要考虑,并且它是会阻塞主线程的。

**常用性:**在Spring框架中,AOP是一个强大的工具,用于实现诸如日志记录、事务管理等横切关注点。因此,使用AOP记录接口执行时间是一种非常常见和推荐的做法。

ApplicationListener

首先我们在项目中创建一个类 ,比如就叫 TakeTimeCountListener,然后实现 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());@Overridepublic void onApplicationEvent(ServletRequestHandledEvent event) {Throwable failureCause = event.getFailureCause() ;if (failureCause != null) {logger.warn("错误原因: {}", failureCause.getMessage());}// 比如我这里只记录接口响应时间大于1秒的日志if (event.getProcessingTimeMillis() > 1000) {logger.warn("请求客户端地址:{}, 请求URL: {}, 请求Method: {}, 请求耗时:{} ms",event.getClientAddress(),event.getRequestUrl(),event.getMethod(),event.getProcessingTimeMillis());}}
}
2024-06-19 17:14:59.620 [TID: N/A] WARN  [com.springboot.demo.TakeTimeCountListener] 请求客户端地址:0:0:0:0:0:0:0:1, 请求URL: /springboot/test1, 请求Method: GET, 请求耗时:51 ms
2024-06-19 17:14:59.716 [TID: N/A] WARN  [com.springboot.demo.TakeTimeCountListener] 请求客户端地址:0:0:0:0:0:0:0:1, 请求URL: /springboot/test2, 请求Method: GET, 请求耗时:136 ms
2024-06-19 17:14:59.787 [TID: N/A] WARN  [com.springboot.demo.TakeTimeCountListener] 请求客户端地址:0:0:0:0:0:0:0:1, 请求URL: /springboot/test3, 请求Method: POST, 请求耗时:255 ms
2024-06-19 17:14:59.859 [TID: N/A] WARN  [com.springboot.demo.TakeTimeCountListener] 请求客户端地址:0:0:0:0:0:0:0:1, 请求URL: /springboot/test4, 请求Method: POST, 请求耗时:167 ms

优点:

  • 集成性:与Spring MVC框架紧密集成,无需额外配置。
  • 性能:改方法是不会阻塞主线程的,也就是说 该方法在处理的时候,controller 已经正常返回了,可以通过在该方法进行断点调试来验证。
  • 简单易用:实现ApplicationListener接口并监听ServletRequestHandledEvent事件即可。

缺点:

  • 适用范围:主要适用于Spring MVC 框架下的 Web 请求,对于非 Web 接口(如RESTful API)可能不适用。
  • 精度:只能记录整个请求的处理时间,无法精确到具体的方法执行时间。

**常用性:**在Spring MVC应用中,使用ApplicationListener来记录请求处理时间是一种常见做法,但通常用于监控和性能分析,而不是精确记录接口执行时间。

Tomcat

Tomcat 的实现很简单,只需要开启它本身就支持的访问日志就可以了 ,在 SpringBoot 中,我们可以在 properties 或 yaml 文件中增加下面配置:

# 启用Tomcat访问日志
server.tomcat.accesslog.enabled=true  
# 启用缓冲模式,日志会先写入缓冲区,然后定期刷新到磁盘 
server.tomcat.accesslog.buffered=true  
# 指定日志存储目录,这里是相对于项目根目录的logs文件夹  
server.tomcat.accesslog.directory=logs 
# 定义日志文件名的日期格式
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd 
# 定义日志记录的格式  
# 各个字段的意义:  
# %{X-Forwarded-For}i: 请求头中的X-Forwarded-For,通常用于记录客户端真实IP  
# %p: 本地端口  
# %l: 远程用户,通常为'-'  
# %r: 请求的第一行(例如:GET / HTTP/1.1)  
# %t: 请求时间(格式由日志处理器决定)  
# 注意:这里有一个重复的%r,可能是个错误,通常第二个%r不需要  
# %s: HTTP状态码  
# %b: 响应字节数,不包括HTTP头,如果为0则不输出  
# %T: 请求处理时间(以秒为单位)  
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  # 启用缓冲模式,日志会先写入缓冲区,然后定期刷新到磁盘 directory: logs  # 指定日志存储目录,这里是相对于项目根目录的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

优点:

  • 集成性:Tomcat 内置功能,无需额外代码或配置。
  • 全面性:记录所有通过 Tomcat 处理的请求和响应信息。

缺点:

  • 性能:访问日志可能会对 Tomcat 性能产生一定影响。
  • 精度:同样只能记录整个请求的处理时间,无法精确到具体的方法执行时间。
  • 配置复杂性:对于复杂的日志格式或需求,可能需要修改 Tomcat 的配置文件。

**常用性:**Tomcat 的访问日志通常用于监控 Web 服务器的访问情况,如 IP 地址、请求路径、HTTP 状态码等。虽然它可以记录请求处理时间,但通常不会用于精确的性能分析或接口执行时间记录。

相关文章:

SpringBoot 统计接口调用耗时的多种方式

在实际开发中&#xff0c;了解项目中接口的响应时间是必不可少的事情。SpringBoot 项目支持监听接口的功能也不止一个&#xff0c;接下来我们分别以 AOP、ApplicationListener、Tomcat 三个方面去实现三种不同的监听接口响应时间的操作。 AOP 首先我们在项目中创建一个类 &am…...

Linux系统安装Ruby语言

Ruby是一种面向对象的脚本语言&#xff0c;由日本的计算机科学家松本行弘设计并开发&#xff0c;Ruby的设计哲学强调程序员的幸福感&#xff0c;致力于简化编程的复杂性&#xff0c;并提供一种既强大又易于使用的工具。其语法简洁优雅&#xff0c;易于阅读和书写&#xff0c;使…...

网络安全练气篇——OWASP TOP 10

1、什么是OWASP&#xff1f; OWASP&#xff08;开放式Web应用程序安全项目&#xff09;是一个开放的社区&#xff0c;由非营利组织 OWASP基金会支持的项目。对所有致力于改进应用程序安全的人士开放&#xff0c;旨在提高对应用程序安全性的认识。 其最具权威的就是“10项最严重…...

python实现进度条的方法和实现代码

在Python中&#xff0c;有多种方式可以实现进度条。这里&#xff0c;我将介绍七种常见的方法&#xff1a;使用tqdm&#xff08;这是一个外部库&#xff0c;非常流行且易于使用&#xff09;、rich、click、progressbar2等库以及纯Python的print函数与time库来模拟进度条。 目录…...

被拷打已老实!面试官问我 #{} 和 ${} 的区别是什么?

引言&#xff1a;在使用 MyBatis 进行数据库操作时&#xff0c;#{} 和 ${} 的区别是面试中常见的问题&#xff0c;对理解如何在 MyBatis 中安全有效地处理 SQL 语句至关重要。正确使用这两种占位符不仅影响应用的安全性&#xff0c;还涉及到性能优化。 题目 被拷打已老实&…...

C# —— while循环语句

作用 让顺序执行的代码 可以停下来 循环执行某一代码块 // 条件分支语句: 让代码产生分支 进行执行 // 循环语句 : 让代码可以重复执行 语法 while循环 while (bool值) { 循环体(条件满足时执行的代码块) …...

力扣第205题“同构字符串”

在本篇文章中&#xff0c;我们将详细解读力扣第205题“同构字符串”。通过学习本篇文章&#xff0c;读者将掌握如何使用哈希表来解决这一问题&#xff0c;并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释&#xff0c;以便于理解。 问题描述 力扣第205题“…...

探索RESTful API开发,构建可扩展的Web服务

介绍 当我们浏览网页、使用手机应用或与各种互联网服务交互时&#xff0c;我们经常听到一个术语&#xff1a;“RESTful API”。它听起来很高深&#xff0c;但实际上&#xff0c;它是构建现代网络应用程序所不可或缺的基础。 什么是RESTful API&#xff1f; 让我们将RESTful …...

苹果安卓网页的H5封装成App的应用和原生开发的应用有什么不一样?

H5封装类成App的应用和原生应用有什么不一样&#xff1f;——一对比谈优缺点 1. 开发速度和复用性 H5封装的App优势&#xff1a;一次编写&#xff0c;多平台运行。你只需要使用一种语言编写代码&#xff0c;就可以发布到不同的平台&#xff0c;降低开发成本。 原生应用优势&…...

IO流2.

字符流-->字符流的底层其实就是字节流 public class Stream {public static void main(String[] args) throws IOException {//1.创建对象并关联本地文件FileReader frnew FileReader("abc\\a.txt");//2.读取资源read()int ch;while((chfr.read())!-1){System.out…...

详解MySQL中的PERCENT_RANK函数

目录 1. 引入1. 基本使用2&#xff1a;分组使用3&#xff1a;处理重复值4. 使用优势4.1 手动计算百分等级4.2 使用 PERCENT_RANK 的优势4.3 使用 PERCENT_RANK 5. 总结 在 MySQL 中&#xff0c;PERCENT_RANK 函数用于计算一个值在其分组中的百分等级。 它的返回值范围是从 0 …...

宏任务与微任务

一、宏任务 1、概念 指消息队列中等地被主线程执行的事件 2、种类 script主代码块、setTimeout 、setInterval 、nodejs的setImmediate 、MessageChannel&#xff08;react的fiber用到&#xff09;、postMessage、网络I/O、文件I/O、用户交互的回调等事件、UI渲染事件&#x…...

昇思大模型学习·第一天

mindspore快速入门回顾 导入mindspore包 处理数据集 下载mnist数据集进行数据集预处理 MnistDataset()方法train_dataset.get_col_names() 打印列名信息使用create_tuple_iterator 或create_dict_iterator对数据集进行迭代访问 网络构建 mindspore.nn: 构建所有网络的基类用…...

python调用chatgpt

简单写了一下关于文本生成接口的调用&#xff0c;其余更多的调用方法可在官网查看 import os from dotenv import load_dotenv, find_dotenv from openai import OpenAI import httpxdef gpt_config():# 为了安全起见&#xff0c;将key写到当前项目根目录下的.env文件中# find…...

YOLOV8 目标检测:训练自定义数据集

1、下载 yolov8项目&#xff1a;ultralytics/ultralytics&#xff1a;新增 - PyTorch 中的 YOLOv8 &#x1f680; > ONNX > OpenVINO > CoreML > TFLite --- ultralytics/ultralytics: NEW - YOLOv8 &#x1f680; in PyTorch > ONNX > OpenVINO > CoreM…...

动态更新自建的Redis连接池连接数量

/*** 定时更新Redis连接池信息&#xff0c;防止资源让费*/private static final ScheduledThreadPoolExecutor DYNAMICALLY_UPDATE_REDIS_POOL_THREAD new ScheduledThreadPoolExecutor(1, new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {Thread thread …...

浅谈设计师的设计地位

在当今这个创意无限的时代&#xff0c;设计师的地位日益凸显。他们以独特的视角和精湛的技能&#xff0c;为我们的生活带来了无尽的色彩与灵感。然而&#xff0c;随着行业的不断发展&#xff0c;设计师如何在众多同行中脱颖而出&#xff0c;提升自己的设计地位呢&#xff1f;答…...

C/C++ string模拟实现

1.模拟准备 1.1因为是模拟string&#xff0c;防止与库发生冲突&#xff0c;所以需要命名空间namespace隔离一下&#xff0c;我们来看一下基本内容 namespace yx {class string{private://char _buff[16]; lunix下小于16字节就存buff里char* _str;size_t _size;size_t _capac…...

微信小程序学习(八):behaviors代码复用

小程序的 behaviors 方法是一种代码复用的方式&#xff0c;可以将一些通用的逻辑和方法提取出来&#xff0c;然后在多个组件中复用&#xff0c;从而减少代码冗余&#xff0c;提高代码的可维护性。 如果需要 behavior 复用代码&#xff0c;需要使用 Behavior() 方法&#xff0c…...

【The design pattern of Attribute-Based Dynamic Routing Pattern (ADRP)】

In ASP.NET Core, routing is one of the core functionalities that maps HTTP requests to the corresponding controller actions. While “Route-Driven Design Pattern” is a coined name for a design pattern, we can construct a routing-centric design pattern base…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...