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