
本文探讨了在Spring Boot应用中,如何在利用ExceptionHandler进行统一异常处理的同时,准确记录方法执行时间。文章提供了两种核心策略:一是利用Spring AOP实现横切关注点,在方法执行前后及异常捕获时统一测量时间;二是设计自定义异常,将执行时间封装传递给ExceptionHandler。通过详细的代码示例和专业分析,帮助开发者选择并实施最适合其应用场景的执行时间记录与异常处理方案。
在现代企业级应用开发中,监控方法执行时间是性能优化和问题诊断的关键环节。特别是在Spring Boot应用中,当结合统一异常处理机制(如@ExceptionHandler)时,如何在捕获异常的同时依然准确记录相关方法的执行时间,成为了一个常见的挑战。传统的try-catch块内手动计时方式,虽然直接,但在大规模应用中会导致代码冗余,且难以与ExceptionHandler无缝集成。本文将深入探讨两种高效且优雅的解决方案。
Spring 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;
}
}为了更灵活地控制哪些方法需要被计时,我们可以定义一个自定义注解,并在切点中使用它。
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";
}
}如果应用已经大量依赖@ExceptionHandler进行统一异常处理,并且希望将执行时间信息也传递给它,那么可以考虑定义一个包含执行时间字段的自定义异常。
首先,创建一个继承自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;
}
}在业务方法中,使用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);
}
}
}在全局或局部的@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;
}
}
}在Spring Boot应用中记录方法执行时间并结合异常处理,可以根据具体需求选择不同的策略:
在实际开发中,AOP方案通常被认为是更“Spring Style”和更具扩展性的选择,因为它将横切关注点与业务逻辑清晰分离。然而,如果项目规模较小或有特定限制,自定义异常方案也是一个可行的替代方案。选择哪种方案,应综合考虑项目的架构、团队的熟悉程度以及对代码侵入性的要求。无论选择哪种,关键在于确保执行时间能够被准确测量,并在异常发生时也能被妥善记录和处理。
以上就是Spring Boot中优雅地记录执行时间与异常处理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号