Springboot三大特性 springboot优雅地捕获异常

本文探讨了在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@Componentpublic 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 {}登录后复制然后在需要计时的业务方法上添加此注解:
@Servicepublic 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笔记, 像聊天一样随时随地记录每一个想法,打造属于你的个人知识库,成为你的外挂大脑
195 查看详情
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;@Servicepublic 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;@ControllerAdvicepublic 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应用中记录方法执行时间并结合异常处理,可以根据具体需求选择不同的策略:
Spring AOP方案: 适用于需要对大量方法进行统一性能监控和日志记录的场景。它能够实现代码的完全解耦,使业务逻辑保持纯净。通过自定义注解和切点,可以灵活控制监控范围。自定义异常方案: 适用于已经大量使用ExceptionHandler且不希望引入AOP,或者需要在异常处理层直接获取精确执行时间信息的场景。虽然需要在业务代码中手动封装异常,但提供了直接将时间信息传递给ExceptionHandler的途径。在实际开发中,AOP方案通常被认为是更“Spring Style”和更具扩展性的选择,因为它将横切关注点与业务逻辑清晰分离。然而,如果项目规模较小或有特定限制,自定义异常方案也是一个可行的替代方案。选择哪种方案,应综合考虑项目的架构、团队的熟悉程度以及对代码侵入性的要求。无论选择哪种,关键在于确保执行时间能够被准确测量,并在异常发生时也能被妥善记录和处理。
以上就是Spring Boot中优雅地记录执行时间与异常处理的详细内容,更多请关注乐哥常识网其它相关文章!
相关标签: java app 工具 应用开发 red spring spring boot 架构 封装 try catch 继承 性能优化 应用开发 大家都在看: Java中IOException的包定义与异常继承机制解析 Java中从静态成员类派生枚举:反射验证与包装器模式 如何在 Java 中从静态类成员获取枚举(及最佳实践) Java中从静态类成员动态生成枚举的策略与实践 Windows AppLocker环境下Java JNA动态库加载策略优化指南