在开发Spring Boot项目时,咱们常常会遇到性能方面的问题。要想精准定位并解决这些问题,统计方法的执行时间就显得尤为重要。通过了解各个方法的耗时情况,我们能够快速找出性能瓶颈,进而优化系统响应速度。今天,我就来给大家分享在Spring Boot框架里实现方法耗时统计的七种实用方法。

一、手动使用StopWatch

在众多统计方法耗时的方式中,手动使用Spring提供的StopWatch类是最直接的一种。这种方法简单易懂,特别适合做临时性的性能测试。

import org.springframework.util.StopWatch; @Service public class UserService { public User findUserById(Long id) { // 创建StopWatch实例,用于记录时间 StopWatch stopWatch = new StopWatch(); // 开始计时 stopWatch.start(); // 业务逻辑,从数据库中根据id查找用户 User user = userRepository.findById(id).orElse(null); // 停止计时 stopWatch.stop(); // 输出方法执行耗时 System.out.println("findUserById方法耗时:" + stopWatch.getTotalTimeMillis() + "ms"); return user; } } 

这种方式的优点很明显,它简单直观,不需要额外配置其他东西。但缺点也不容忽视,它会侵入业务代码,使代码看起来不够优雅。而且,如果要监控多个方法的耗时,就需要在每个方法里都手动添加这些代码,工作量较大。

二、借助AOP实现全局方法耗时统计

AOP,也就是面向切面编程,用它来统计方法耗时是个很不错的选择。使用AOP,我们可以在不改动原有业务代码的基础上,统一处理耗时统计的逻辑。

首先,要在项目中添加AOP依赖,在pom.xml文件中添加如下代码:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 

添加完依赖后,接着创建切面类:

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @Aspect @Component public class MethodTimeAspect { // 定义切点,这里表示匹配com.example.demo.service包下所有类的所有方法 @Pointcut("execution(* com.example.demo.service.*.*(..))") public void serviceMethodPointcut() {} // 环绕通知,在目标方法执行前后进行计时 @Around("serviceMethodPointcut()") public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable { // 创建StopWatch实例 StopWatch stopWatch = new StopWatch(); // 开始计时 stopWatch.start(); // 执行目标方法 Object result = joinPoint.proceed(); // 停止计时 stopWatch.stop(); // 获取目标方法的名称 String methodName = joinPoint.getSignature().getName(); // 输出方法执行耗时 System.out.println("方法[" + methodName + "]耗时:" + stopWatch.getTotalTimeMillis() + "ms"); return result; } } 

这种方式的优势在于,代码没有侵入性,所有的耗时统计逻辑都可以统一管理,配置起来也很灵活。不过,它也有不足的地方,对于一些特定方法的定制化需求,实现起来不是那么方便。

三、自定义注解+AOP实现精细化控制

如果想要更精确地控制哪些方法需要进行耗时统计,我们可以把自定义注解和AOP结合起来使用。

先创建自定义注解:

import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TimeLog { // 注解的值,用于描述方法,默认为空 String value() default ""; } 

然后,创建切面类来处理带有该注解的方法:

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @Aspect @Component public class TimeLogAspect { @Around("@annotation(com.example.demo.annotation.TimeLog)") public Object timeLogAround(ProceedingJoinPoint joinPoint) throws Throwable { // 获取方法签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取方法上的TimeLog注解 TimeLog timeLog = signature.getMethod().getAnnotation(TimeLog.class); // 根据注解的值来确定方法描述,如果注解值为空,则使用方法名 String methodDesc = timeLog.value().isEmpty() ? signature.getMethod().getName() : timeLog.value(); // 创建StopWatch实例 StopWatch stopWatch = new StopWatch(); // 开始计时 stopWatch.start(); // 执行目标方法 Object result = joinPoint.proceed(); // 停止计时 stopWatch.stop(); // 输出方法执行耗时 System.out.println("方法[" + methodDesc + "]耗时:" + stopWatch.getTotalTimeMillis() + "ms"); return result; } } 

使用示例如下:

@Service public class ProductService { @TimeLog("查询商品详情") public Product getProductDetail(Long id) { // 业务逻辑,从数据库中根据id查找商品详情 return productRepository.findById(id).orElse(null); } } 

这种方法的好处是可以进行更精细的控制,注解还能携带更多信息,方便我们根据不同需求进行定制。但缺点是需要手动在每个要监控的方法上添加注解。

四、利用拦截器统计Controller接口耗时

要是我们只关心Controller层接口的耗时情况,那么使用Spring的拦截器是个不错的选择。

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; @Component public class ApiTimeInterceptor implements HandlerInterceptor { // 使用ThreadLocal来存储请求开始时间,避免多线程环境下的数据冲突 private ThreadLocal<Long> startTime = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 在请求处理前,记录开始时间 startTime.set(System.currentTimeMillis()); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 在请求处理后,获取结束时间 long endTime = System.currentTimeMillis(); // 计算请求执行时间 long executionTime = endTime - startTime.get(); // 获取请求的URI String uri = request.getRequestURI(); // 输出接口执行耗时 System.out.println("接口[" + uri + "]耗时:" + executionTime + "ms"); // 移除ThreadLocal中的数据,避免内存泄漏 startTime.remove(); } } 

别忘了注册拦截器:

import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { private final ApiTimeInterceptor apiTimeInterceptor; public WebConfig(ApiTimeInterceptor apiTimeInterceptor) { this.apiTimeInterceptor = apiTimeInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { // 添加拦截器,并指定要拦截的路径 registry.addInterceptor(apiTimeInterceptor).addPathPatterns("/api/"); } } 

这种方式专注于Web接口的性能监控,可以对接口进行统一管理。但它只能监控Controller层的方法,对于内部服务方法的耗时监控就无能为力了。

五、借助Actuator + Micrometer实现细粒度监控

Spring Boot Actuator和Micrometer集成后,可以实现更专业的性能指标收集。

先添加相关依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> 

接着,使用Micrometer进行方法计时:

import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.springframework.stereotype.Service; @Service public class OrderService { private final MeterRegistry meterRegistry; public OrderService(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } public Order createOrder(OrderRequest request) { // 开始计时 Timer.Sample sample = Timer.start(meterRegistry); // 业务逻辑,处理订单 Order order = processOrder(request); // 停止计时,并记录到指定的计时器中 sample.stop(meterRegistry.timer("order.creation.time")); return order; } } 

最后,配置Actuator暴露指标:

management: endpoints: web: exposure: include: metrics,prometheus metrics: export: prometheus: enabled: true 

这种方式可以收集专业的性能指标,还能和Prometheus、Grafana等监控系统集成,非常适合生产环境。不过,它的配置相对复杂一些,需要我们花点时间去学习和理解。

六、通过Filter实现请求耗时统计

创建一个Filter实现类,能够记录每次HTTP请求的开始时间和结束时间,进而计算出整个请求的耗时。

import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; @Component public class TimingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 记录请求开始时间 long startTime = System.currentTimeMillis(); // 继续处理请求 chain.doFilter(request, response); // 记录请求结束时间 long endTime = System.currentTimeMillis(); // 计算请求执行时间 long executionTime = endTime - startTime; // 获取请求的URI String requestUri = ((HttpServletRequest) request).getRequestURI(); // 输出请求执行耗时 System.out.println("请求[" + requestUri + "]耗时:" + executionTime + "ms"); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} } 

这种方式可以全局监控所有Web请求的耗时情况,实现起来也比较简单。但它只能提供整体请求的耗时信息,没办法深入到具体业务逻辑的执行时间。

七、利用ServletRequestHandledEvent统计请求处理耗时

Spring Boot提供了ServletRequestHandledEvent事件,利用它可以监控HTTP请求的处理时间,这种方式适合对所有请求进行全局监控。

首先,创建事件监听器:

import org.springframework.context.ApplicationListener; import org.springframework.web.context.request.ServletRequestHandledEvent; import org.springframework.stereotype.Component; @Component public class RequestTimingListener implements ApplicationListener<ServletRequestHandledEvent> { @Override public void onApplicationEvent(ServletRequestHandledEvent event) { // 输出请求的URL和处理时间 System.out.println("请求[" + event.getRequestUrl() + "]耗时:" + event.getProcessingTimeMillis() + "ms"); } } 

这种方法的优点是不需要修改现有代码,就能实现全局请求的耗时监控。但它不支持对请求进行自定义粒度的控制。

总结与对比

在Spring Boot项目中,这七种方法耗时统计方式各有千秋。在实际应用中,我们可以根据具体场景来选择合适的方法:

  • 如果你只是临时做个性能测试,想要快速实现方法耗时统计,那么手动使用StopWatch就很合适。
  • 要是想对整个服务层进行性能监控,全局AOP是个不错的选择。
  • 当需要精细化控制,只监控关键方法时,自定义注解+AOP的方式更能满足需求。
  • 专注于Web接口性能监控的话,使用拦截器就可以。
  • 在生产环境中,希望和专业监控系统集成,实现更专业的性能指标收集,Actuator + Micrometer是最佳选择。
  • 对于简单的全局Web请求耗时监控,Filter方式轻量级又实用。
  • 如果不想改动现有代码,只想全局监控HTTP请求处理时间,ServletRequestHandledEvent就能派上用场。

通过合理选择和运用这些方法,我们能够更高效地优化Spring Boot项目的性能。希望大家在实际开发中都能熟练运用,以后碰到耗时统计问题,都是小case啦!