当前位置: 首页 > 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…...

2713. 矩阵中严格递增的单元格数

题目 给定一个 m x n 的整数矩阵 mat&#xff0c;我们需要找出从某个单元格出发可以访问的最大单元格数量。移动规则是可以从当前单元格移动到同一行或同一列的任何其他单元格&#xff0c;但目标单元格的值必须严格大于当前单元格的值。需要返回最大可访问的单元格数量。 示例…...

git创建子模块

有种情况我们经常会遇到&#xff1a;某个工作中的项目需要包含并使用另一个项目。 也许是第三方库&#xff0c;或者你独立开发的&#xff0c;用于多个父项目的库。 现在问题来了&#xff1a;你想要把它们当做两个独立的项目&#xff0c;同时又想在一个项目中使用另一个。 Git …...

把Deepin塞进U盘,即插即用!Deepin To Go来袭

前言 小伙伴之前在某篇文章下留言说&#xff1a;把Deepin塞进U盘的教程。 这不就来了吗&#xff1f; 事实是可以的。这时候你要先做点小准备&#xff1a; 一个大小为8GB或以上的普通U盘 一个至少64GB或以上的高速U盘 一个Deepin系统镜像文件 普通U盘的大概介绍&#xff1…...

​​给【AI硬件】创业者的论文、开源项目和产品整理

一、AI 硬件精选论文 《DrEureka: Language Model Guided Sim-To-Real Transfer》 瑜伽球上遛「狗」这项研究由宾夕法尼亚大学、 NVIDIA 、得克萨斯大学奥斯汀分校的研究者联合打造&#xff0c;并且完全开源。他们提出了 DrEureka&#xff08;域随机化 Eureka&#xff09;&am…...

模拟面试题卷二

1. 什么是JavaEE框架&#xff0c;你能列举一些常用的JavaEE框架吗&#xff1f; 答&#xff1a;JavaEE框架是一套用于开发企业级应用的技术规范和工具集合。常用的JavaEE框架有Spring、Hibernate、Struts、JSF等。 2. 请解释一下面向对象技术和设计原则是什么&#xff0c;你能…...

22种常用设计模式示例代码

文章目录 创建型模式结构型模式行为模式 仓库地址https://github.com/Xiamu-ssr/DesignPatternsPractice 参考教程 refactoringguru设计模式-目录 创建型模式 软件包复杂度流行度工厂方法factorymethod❄️⭐️⭐️⭐️抽象工厂abstractfactory❄️❄️⭐️⭐️⭐️生成器bui…...

Java面试题:对比ArrayList和LinkedList的内部实现,以及它们在不同场景下的适用性

ArrayList和LinkedList是Java中常用的两个List实现&#xff0c;它们在内部实现和适用场景上有很大差异。下面是详细的对比分析&#xff1a; 内部实现 ArrayList 数据结构&#xff1a;内部使用动态数组&#xff08;即一个可变长的数组&#xff09;实现。存储方式&#xff1a;…...

ping: www.baidu.com: 未知的名称或服务(IP号不匹配)

我用的是VMware上的Red Hat Enterprise Linux 9&#xff0c;出现了能联网但ping不通外网的情况。 问题描述&#xff1a;设置中显示正常连接&#xff0c;而且虚拟机右上角有联网的图标&#xff0c;但不能通外网。 按照网上教程修改了/etc/resolv.conf和/etc/sysconfig/network-…...

谷神前端组件增强:子列表

谷神Ag-Grid导出Excel // 谷神Ag-Grid导出Excel let allDiscolumns detailTable.getAllDisColumns() let columnColIds columns.map(column > column.colId) let columnKeys columnColIds.filter(item > ![select, "_OPT_FIELD_"].includes(item)) detailT…...

测试cudaStream队列的深度

测试cudaStream队列的深度 一.代码二.编译运行[得出队列深度为512] 以下代码片段用于测试cudaStream队列的深度 方法: 主线程一直发任务,启一个线程cudaEventQuery查询已完成的任务,二个计数器的值相减 一.代码 #include <iostream> #include <thread> #include …...

住房和城乡建设部干部学院网站/搜外网

Rayeager PX2开发板具有非常强大的多媒体处理能力&#xff0c;如果需要调用硬件加速针对普通媒体文件/码流进行解码&#xff0c;只需按照安卓标准调用多媒体相关接口即可。针对一些行业用户的特殊需求&#xff0c;Rayeager PX2实际上也开放了接口可以对H.264等裸码流进行解码。…...

做网站需要雇什么人/职业技能培训

首先写一个config类 在类上加入注解 标注为一个spring配置类 Configuration public class SpringConfig {} 在里面注册bean Bean相当于bean标签 别忘了自动注入 package config;import org.springframework.context.annotation.Bean; import org.springframework.context.a…...

软件工程学什么及就业前景/seo查询是什么

组件间的关系&#xff1a; 父子组件 兄弟组件&#xff08;非嵌套组件&#xff09; 祖孙组件&#xff08;跨级组件&#xff09; 几种通信方式&#xff1a; 1.props&#xff1a;(1).children props(2).render props2.消息订阅-发布&#xff1a;pubs-sub、event等等3.集中式管理…...

垫江集团网站建设/seo课程培训学校

linux系统时间有两个&#xff0c;一个是硬件时间&#xff0c;即BIOS时间&#xff0c;就是我们进行CMOS设置时看到的时间&#xff0c;另一个是系统时间&#xff0c;是linux系统Kernel时间。当Linux启动时&#xff0c;系统Kernel会去读取硬件时钟的设置&#xff0c;然后系统时钟就…...

php动态网站开发论文/重庆森林经典台词截图

问题背景&#xff1a;今天同事在做DB双机切换的时候发现oracle的spfile文件作为DG_DATA 下的一个资源&#xff0c;本意是在DG_ORA下后来发现spfile的路径建错了&#xff0c;但是重建带来一个问题启动的时候使用默认启动方式spfile&#xff0c;在此场景下直接修改spfile的路径是…...

wordpress生成推广链接/学网络运营在哪里学比较好

在项目上使用到了滚动条插件&#xff0c;但是使用var & layui.$&#xff0c;会影响到插件。 错误提示&#xff1a; Uncaught TypeError: $(...).perfectScrollbar is not a function 转载于:https://www.cnblogs.com/ybixian/p/9209302.html...