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

SpringBoot计时一次请求耗时

StopWatch是Spring框架提供的一个简单而强大的计时工具类,用于精确测量代码执行时间。它可以帮助开发者快速分析程序的性能瓶颈,从而优化代码,提高程序运行效率。StopWatch基于纳秒级别的时间计算,支持多个任务的计时,并且可以方便地输出计时结果。

准备工作

请求数据重用工具(缓存请求体)

默认情况下,HttpServletRequest 的输入流 (getInputStream()) 只能被读取一次。

如果想多次读取请求体内容(例如本例中在请求处理之前记录时间,请求处理完成之后再次记录时间,计算时间差作为一次请求所需时长),就需要使用 HttpServletRequestWrapper 来封装并缓存请求体内容。

com/zibocoder/plugins/web/utils/RepeatedlyRequestWrapper.java

package com.zibocoder.plugins.web.utils;import cn.hutool.core.io.IoUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.zibocoder.plugins.common.constant.CommonConst;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;/*** @description 允许请求体被多次读取*      原生的 HttpServletRequest.getInputStream() 只能被读取一次。*      该类通过将请求体缓存为字节数组 byte[] body,实现了多次读取的能力。* @author zibocoder* @date 2025/7/1 14:54:23*/
@Slf4j
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {private static final int MAX_BODY_SIZE = 1024 * 1024;  // 1MBprivate final byte[] body;public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {super(request);// 确保请求和响应使用 UTF-8 编码request.setCharacterEncoding(CommonConst.UTF8);response.setCharacterEncoding(CommonConst.UTF8);int contentLength = request.getContentLength();if (contentLength > MAX_BODY_SIZE) {throw new IOException("请求体过大,超过允许的最大限制");}// 使用 Hutool 工具类一次性读取请求体并保存到内存中。body = request.getInputStream() != null ? IoUtil.readBytes(request.getInputStream(), false) : new byte[0];// 再次检查实际读取到的内容长度,防止伪造 Content-Length 或 chunked 编码下的恶意攻击if (body.length > MAX_BODY_SIZE) {throw new IOException("请求体过大(根据实际内容判断),超过允许的最大限制:" + body.length + " bytes");}if (!CommonConst.PROD_PROFILE.equals(SpringUtil.getActiveProfile())) {log.info("缓存请求体大小: {} bytes", body.length);  // 日志增强调试能力}}/*** 重写 getReader() 方法,返回缓存的请求体字节数组* 使用 InputStreamReader 将字节数组转换为字符流,并返回 BufferedReader*/@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}/*** 重写 getInputStream() 方法,返回缓存的请求体字节数组* 使用 ByteArrayInputStream 包装原始 body 字节数组,实现可重复读取的 ServletInputStream*/@Overridepublic ServletInputStream getInputStream() {final ByteArrayInputStream bais = new ByteArrayInputStream(body);return new ServletInputStream() {@Overridepublic int read() {return bais.read();}@Overridepublic int available() {return body.length;}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {throw new UnsupportedOperationException("不支持异步读取,请勿调用 setReadListener()");}};}
}

时间统计拦截器 StopWatch

com/zibocoder/plugins/web/interceptor/WebInvokeTimeInterceptor.java

package com.zibocoder.plugins.web.interceptor;import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.zibocoder.plugins.common.constant.CommonConst;
import com.zibocoder.plugins.web.utils.RepeatedlyRequestWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.StopWatch;
import org.springframework.web.servlet.HandlerInterceptor;import java.io.BufferedReader;
import java.util.Map;/*** @Description 调用时间统计拦截器,统计 web 请求的调用时间*              非 prod 环境下有效*              StopWatch 是 Spring 框架提供的一个简单而强大的计时工具类,用于精确测量代码执行时间 https://cloud.baidu.com/article/3277329* @Author zibocoder* @Date 2025/7/1 08:44:11*/
@Slf4j
public class WebInvokeTimeInterceptor implements HandlerInterceptor {// 线程安全的计时器缓存,存放 StopWatch 对象,用于统计 Web 请求的执行时间private final static ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();/*** 请求处理之前执行,返回 true 表示继续处理,返回 false 表示取消处理*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 非生产环境(获取当前的环境配置,当有多个环境配置时,只获取第一个)if (!CommonConst.PROD_PROFILE.equals(SpringUtil.getActiveProfile())) {String url = request.getMethod() + " " + request.getRequestURI();// 打印请求参数if (isJsonRequest(request)) { //数据类型为json的数据,通常需要从请求体中以输入流的方式读取并解析数据, 那么后续的控制器方法将无法再读取到数据String jsonParam = "";if (request instanceof RepeatedlyRequestWrapper) {BufferedReader reader = request.getReader();jsonParam = IoUtil.read(reader, true);}log.info("开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);} else { //对于非 JSON 请求,直接使用 request.getParameterMap() 即可安全地读取参数,无需使用 RepeatedlyRequestWrapper, 不会受到输入流只能读取一次的限制Map<String, String[]> parameterMap = request.getParameterMap();if (MapUtil.isNotEmpty(parameterMap)) {String parameters = JSONUtil.toJsonStr(parameterMap);log.info("开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);} else {log.info("开始请求 => URL[{}],无参数", url);}}StopWatch stopWatch = new StopWatch();KEY_CACHE.set(stopWatch);stopWatch.start();}return true;}/*** 请求处理完成之后执行*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {StopWatch stopWatch = KEY_CACHE.get();if (ObjectUtil.isNotNull(stopWatch)) {stopWatch.stop();log.info("结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTotalTimeMillis());KEY_CACHE.remove();}}/*** 判断本次请求的数据类型是否为json** @param request request* @return boolean*/private boolean isJsonRequest(HttpServletRequest request) {String contentType = request.getContentType();if (contentType != null) {return StrUtil.startWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);}return false;}
}

添加拦时间统计截器

web资源配置,时间统计拦截器添加到通用拦截器中

com/zibocoder/plugins/web/config/ResourcesConfigure.java

package com.zibocoder.plugins.web.config;import com.zibocoder.plugins.web.interceptor.WebInvokeTimeInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @description web资源配置* @author zibocoder* @date 2025/7/1 15:38:30*/
@Configuration
public class ResourcesConfigure implements WebMvcConfigurer {/*** 添加拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 全局访问性能拦截registry.addInterceptor(new WebInvokeTimeInterceptor()).addPathPatterns("/**").excludePathPatterns("/favicon.ico");}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {}/*** 跨域配置*/@Beanpublic CorsFilter corsFilter() {CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(true);// 设置访问源地址config.addAllowedOriginPattern("*");// 设置访问源请求头config.addAllowedHeader("*");// 设置访问源请求方法config.addAllowedMethod("*");// 有效期 1800秒config.setMaxAge(1800L);// 添加映射路径,拦截一切请求UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", config);// 返回新的CorsFilterreturn new CorsFilter(source);}
}

效果展示

在这里插入图片描述

http://www.lqws.cn/news/598789.html

相关文章:

  • 数据库编程-ORM
  • 基于Pandas和FineBI的昆明职位数据分析与可视化实现(四)- 职位数据可视化(FineBI)
  • Java-String类静态成员方法深度解析
  • HDMI 2.1 FRL协议的流控机制:切片传输(Slicing)和GAP插入
  • 开关电源和线性电源Multisim电路仿真实验汇总——硬件工程师笔记
  • 【SQL知识】PDO 和 MySQLi 的区别
  • Golang的并发编程实践总结
  • github代码中遇到的问题-解决方案
  • RNN和LSTM
  • flv.js视频/直播流测试demo
  • npm link的使用方法详细介绍
  • 动手实践:如何提取Python代码中的字符串变量的值
  • QML通过XMLHttpRequest实现HTTP通信
  • RocketMQ的广播消息和集群消息有什么区别?
  • 密码学(斯坦福)
  • 突破性进展:超短等离子体脉冲实现单电子量子干涉,为飞行量子比特奠定基础
  • 分布式爬虫数据存储开发实战
  • Hadoop、Spark、Flink 三大大数据处理框架的能力与应用场景
  • (LeetCode 面试经典 150 题) 42. 接雨水 (单调栈)
  • 数据分析与做菜的关系,makedown
  • 630,百度文心大模型4.5系列开源!真香
  • 牛客笔试AI智能监考:革新远程招聘,打造公平高效的笔试新时代
  • 力扣网C语言编程题:寻找两个正序数组的中位数
  • (LeetCode 每日一题) 3330. 找到初始输入字符串 I (字符串)
  • 8.4 Jmter实践不同线程组之间的全局变量的传递和使用
  • opencv入门(5)图像像素的读写操作和算术运算
  • VCenter SSL过期,登录提示HTTP 500错误解决办法
  • 应急响应靶机-linux1-知攻善防实验室
  • 动态库与符号表综合指南
  • Github CLI 快速 clone下载到本地教程