Spring Boot如何实现耗时统计 七种方法方式性能优化必备

后端 潘老师 1小时前 4 ℃ (0) 扫码查看

在开发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啦!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/18382.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】