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

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...

智能职业发展系统:AI驱动的职业规划平台技术解析

智能职业发展系统&#xff1a;AI驱动的职业规划平台技术解析 引言&#xff1a;数字时代的职业革命 在当今瞬息万变的就业市场中&#xff0c;传统的职业规划方法已无法满足个人和企业的需求。据统计&#xff0c;全球每年有超过2亿人面临职业转型困境&#xff0c;而企业也因此遭…...

TJCTF 2025

还以为是天津的。这个比较容易&#xff0c;虽然绕了点弯&#xff0c;可还是把CP AK了&#xff0c;不过我会的别人也会&#xff0c;还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...