首页 > Java > java教程 > 正文

Spring Boot中优雅地记录执行时间与异常处理

霞舞
发布: 2025-12-02 21:55:18
原创
140人浏览过

spring boot中优雅地记录执行时间与异常处理

本文探讨了在Spring Boot应用中,如何在利用ExceptionHandler进行统一异常处理的同时,准确记录方法执行时间。文章提供了两种核心策略:一是利用Spring AOP实现横切关注点,在方法执行前后及异常捕获时统一测量时间;二是设计自定义异常,将执行时间封装传递给ExceptionHandler。通过详细的代码示例和专业分析,帮助开发者选择并实施最适合其应用场景的执行时间记录与异常处理方案。

在现代企业级应用开发中,监控方法执行时间是性能优化和问题诊断的关键环节。特别是在Spring Boot应用中,当结合统一异常处理机制(如@ExceptionHandler)时,如何在捕获异常的同时依然准确记录相关方法的执行时间,成为了一个常见的挑战。传统的try-catch块内手动计时方式,虽然直接,但在大规模应用中会导致代码冗余,且难以与ExceptionHandler无缝集成。本文将深入探讨两种高效且优雅的解决方案。

1. 利用Spring AOP实现执行时间测量与异常封装

Spring AOP(面向切面编程)是解决横切关注点(如日志记录、性能监控、事务管理等)的强大工具。通过定义切面,我们可以在不修改核心业务逻辑的情况下,在方法执行前、执行后或异常抛出时插入额外的行为。

1.1 AOP切面设计

我们可以创建一个@Aspect类,使用@Around通知来环绕目标方法的执行。在@Around通知中,我们可以精确地记录方法的开始和结束时间,并在方法执行过程中捕获任何异常。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;

@Aspect
@Component
public class ExecutionTimeAspect {

    private static final Logger logger = LoggerFactory.getLogger(ExecutionTimeAspect.class);

    // 定义切点,可以根据实际需求调整,例如只作用于特定包下的方法
    // @Pointcut("within(com.example.myapp.service..*)")
    // public void serviceMethods() {}

    // 使用 @Around 通知环绕目标方法
    @Around("@annotation(com.example.myapp.annotation.LogExecutionTime)") // 假设我们定义了一个自定义注解
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        Instant start = Instant.now();
        Object result;
        try {
            // 执行目标方法
            result = joinPoint.proceed();
        } catch (Exception ex) {
            // 捕获异常,记录执行时间
            Instant end = Instant.now();
            long executionTimeMillis = Duration.between(start, end).toMillis();
            logger.error("方法 {} 执行异常,耗时 {} ms. 异常信息: {}", 
                         joinPoint.getSignature().toShortString(), 
                         executionTimeMillis, 
                         ex.getMessage(), 
                         ex);
            // 重新抛出异常,以便ExceptionHandler可以捕获
            throw ex; 
        }

        Instant end = Instant.now();
        long executionTimeMillis = Duration.between(start, end).toMillis();
        logger.info("方法 {} 执行成功,耗时 {} ms.", 
                    joinPoint.getSignature().toShortString(), 
                    executionTimeMillis);
        return result;
    }
}
登录后复制

1.2 自定义注解(可选)

为了更灵活地控制哪些方法需要被计时,我们可以定义一个自定义注解,并在切点中使用它。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
登录后复制

然后在需要计时的业务方法上添加此注解:

@Service
public class MyService {

    @LogExecutionTime
    public String doSomeFancyStuff() throws Exception {
        // 模拟耗时操作
        Thread.sleep(500); 
        // 模拟可能抛出异常
        // if (true) throw new RuntimeException("Something went wrong!");
        return "Task Completed";
    }
}
登录后复制

1.3 优势与注意事项

  • 优势: 代码解耦,业务逻辑更纯粹;集中管理性能监控和异常日志;易于维护和扩展。
  • 注意事项: AOP会引入一定的性能开销;切点表达式需要谨慎定义,避免过度匹配或漏匹配;确保AOP配置正确启用(Spring Boot通常自动配置)。

2. 通过自定义异常传递执行时间到ExceptionHandler

如果应用已经大量依赖@ExceptionHandler进行统一异常处理,并且希望将执行时间信息也传递给它,那么可以考虑定义一个包含执行时间字段的自定义异常。

话袋AI笔记
话袋AI笔记

话袋AI笔记, 像聊天一样随时随地记录每一个想法,打造属于你的个人知识库,成为你的外挂大脑

话袋AI笔记 195
查看详情 话袋AI笔记

2.1 定义包含执行时间的自定义异常

首先,创建一个继承自RuntimeException的自定义异常类,并添加一个字段来存储执行时间。

import java.time.Duration;

public class TimeMeasuredException extends RuntimeException {

    private final Duration executionDuration;
    private final Throwable originalCause; // 用于存储原始异常

    public TimeMeasuredException(Duration executionDuration, Throwable originalCause) {
        super("方法执行异常,耗时: " + executionDuration.toMillis() + " ms. 原始异常: " + originalCause.getMessage(), originalCause);
        this.executionDuration = executionDuration;
        this.originalCause = originalCause;
    }

    public Duration getExecutionDuration() {
        return executionDuration;
    }

    public Throwable getOriginalCause() {
        return originalCause;
    }
}
登录后复制

2.2 修改业务逻辑以抛出自定义异常

在业务方法中,使用try-catch块来测量执行时间,并在捕获到异常时,将其封装进TimeMeasuredException并重新抛出。

import org.springframework.stereotype.Service;
import java.time.Duration;
import java.time.Instant;

@Service
public class MyLegacyService {

    public String doSomethingWithTimingAndException() {
        Instant start = Instant.now();
        try {
            // 模拟一些业务逻辑
            Thread.sleep(300);
            if (true) { // 模拟抛出异常的条件
                throw new IllegalArgumentException("Invalid input data!");
            }
            return "Operation successful";
        } catch (Exception e) {
            Instant end = Instant.now();
            Duration executionTime = Duration.between(start, end);
            // 封装原始异常和执行时间
            throw new TimeMeasuredException(executionTime, e);
        }
    }
}
登录后复制

2.3 ExceptionHandler中处理自定义异常

在全局或局部的@ControllerAdvice中,可以捕获TimeMeasuredException,并从中提取执行时间信息。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(TimeMeasuredException.class)
    public ResponseEntity<ErrorResponse> handleTimeMeasuredException(TimeMeasuredException ex) {
        long executionTimeMillis = ex.getExecutionDuration().toMillis();
        String originalErrorMessage = ex.getOriginalCause().getMessage();

        logger.error("捕获到TimeMeasuredException,方法执行耗时: {} ms. 原始错误: {}", 
                     executionTimeMillis, 
                     originalErrorMessage, 
                     ex);

        ErrorResponse errorResponse = new ErrorResponse(
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            "操作失败,耗时: " + executionTimeMillis + " ms. 详情: " + originalErrorMessage
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // 如果希望捕获所有异常并检查是否为TimeMeasuredException
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneralException(Exception e) {
        if (e instanceof TimeMeasuredException) {
            return handleTimeMeasuredException((TimeMeasuredException) e);
        }

        logger.error("捕获到通用异常: {}", e.getMessage(), e);
        ErrorResponse errorResponse = new ErrorResponse(
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            "服务器内部错误:" + e.getMessage()
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // 假设的错误响应类
    static class ErrorResponse {
        public int status;
        public String message;

        public ErrorResponse(int status, String message) {
            this.status = status;
            this.message = message;
        }
    }
}
登录后复制

2.4 优势与注意事项

  • 优势: 与现有ExceptionHandler机制无缝集成;不需要引入AOP框架(如果项目尚未引入);异常信息更丰富,便于调试。
  • 注意事项: 业务代码中需要手动添加try-catch块来封装异常,可能导致一定程度的代码重复;如果原始异常类型很重要,需要确保TimeMeasuredException能正确传递或封装它。

总结

在Spring Boot应用中记录方法执行时间并结合异常处理,可以根据具体需求选择不同的策略:

  1. Spring AOP方案: 适用于需要对大量方法进行统一性能监控和日志记录的场景。它能够实现代码的完全解耦,使业务逻辑保持纯净。通过自定义注解和切点,可以灵活控制监控范围。
  2. 自定义异常方案: 适用于已经大量使用ExceptionHandler且不希望引入AOP,或者需要在异常处理层直接获取精确执行时间信息的场景。虽然需要在业务代码中手动封装异常,但提供了直接将时间信息传递给ExceptionHandler的途径。

在实际开发中,AOP方案通常被认为是更“Spring Style”和更具扩展性的选择,因为它将横切关注点与业务逻辑清晰分离。然而,如果项目规模较小或有特定限制,自定义异常方案也是一个可行的替代方案。选择哪种方案,应综合考虑项目的架构、团队的熟悉程度以及对代码侵入性的要求。无论选择哪种,关键在于确保执行时间能够被准确测量,并在异常发生时也能被妥善记录和处理。

以上就是Spring Boot中优雅地记录执行时间与异常处理的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号