0

0

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

霞舞

霞舞

发布时间:2025-12-02 21:55:18

|

207人浏览过

|

来源于php中文网

原创

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进行统一异常处理,并且希望将执行时间信息也传递给它,那么可以考虑定义一个包含执行时间字段的自定义异常。

Tellers AI
Tellers AI

Tellers是一款自动视频编辑工具,可以将文本、文章或故事转换为视频。

下载

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 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 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框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

103

2025.08.06

spring boot框架优点
spring boot框架优点

spring boot框架的优点有简化配置、快速开发、内嵌服务器、微服务支持、自动化测试和生态系统支持。本专题为大家提供spring boot相关的文章、下载、课程内容,供大家免费下载体验。

135

2023.09.05

spring框架有哪些
spring框架有哪些

spring框架有Spring Core、Spring MVC、Spring Data、Spring Security、Spring AOP和Spring Boot。详细介绍:1、Spring Core,通过将对象的创建和依赖关系的管理交给容器来实现,从而降低了组件之间的耦合度;2、Spring MVC,提供基于模型-视图-控制器的架构,用于开发灵活和可扩展的Web应用程序等。

389

2023.10.12

Java Spring Boot开发
Java Spring Boot开发

本专题围绕 Java 主流开发框架 Spring Boot 展开,系统讲解依赖注入、配置管理、数据访问、RESTful API、微服务架构与安全认证等核心知识,并通过电商平台、博客系统与企业管理系统等项目实战,帮助学员掌握使用 Spring Boot 快速开发高效、稳定的企业级应用。

68

2025.08.19

Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性
Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性

Spring Boot 是一个基于 Spring 框架的 Java 开发框架,它通过 约定优于配置的原则,大幅简化了 Spring 应用的初始搭建、配置和开发过程,让开发者可以快速构建独立的、生产级别的 Spring 应用,无需繁琐的样板配置,通常集成嵌入式服务器(如 Tomcat),提供“开箱即用”的体验,是构建微服务和 Web 应用的流行工具。

33

2025.12.22

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

114

2025.12.24

PHP 高并发与性能优化
PHP 高并发与性能优化

本专题聚焦 PHP 在高并发场景下的性能优化与系统调优,内容涵盖 Nginx 与 PHP-FPM 优化、Opcode 缓存、Redis/Memcached 应用、异步任务队列、数据库优化、代码性能分析与瓶颈排查。通过实战案例(如高并发接口优化、缓存系统设计、秒杀活动实现),帮助学习者掌握 构建高性能PHP后端系统的核心能力。

98

2025.10.16

PHP 数据库操作与性能优化
PHP 数据库操作与性能优化

本专题聚焦于PHP在数据库开发中的核心应用,详细讲解PDO与MySQLi的使用方法、预处理语句、事务控制与安全防注入策略。同时深入分析SQL查询优化、索引设计、慢查询排查等性能提升手段。通过实战案例帮助开发者构建高效、安全、可扩展的PHP数据库应用系统。

80

2025.11.13

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

72

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.6万人学习

C# 教程
C# 教程

共94课时 | 7万人学习

Java 教程
Java 教程

共578课时 | 47.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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