你在使用 Spring Boot3 开发后端项目时,有没有被项目中四处散落的异常处理代码搞得焦头烂额?不仅让代码结构混乱,后期维护也困难重重。其实,Spring Boot3 提供了强大的全局异常处理机制,能帮我们巧妙解决这些问题。今天,就来一起看看如何搭建这一机制,让代码更优雅、更易维护。
背景介绍
在开发大型项目时,异常处理是不可避免的一环。不同模块抛出的各种异常,若不能统一管理,就会导致代码中充斥着大量重复的异常处理逻辑。这样不仅增加了代码量,还降低了代码的可读性和可维护性。
以一个电商系统为例,在订单模块、用户模块、商品模块等不同部分,都可能出现各种各样的异常。比如订单模块中,可能因为库存不足产生
InsufficientStockException;用户模块可能因为用户名已存在出现
UsernameAlreadyExistsException;商品模块可能因为商品信息缺失出现
MissingProductInfoException。如果在每个抛出异常的地方都单独编写处理逻辑,不仅代码冗余,而且一旦需要修改异常处理方式,就需要在众多地方进行调整,极易出错。
据不完全统计,在互联网大厂的后端服务故障中,因接口参数校验问题引发的故障占比高达 30%,而参数校验失败往往会导致异常抛出。因此,实现高效、准确的异常处理,对保障系统的稳定运行和业务的正常开展至关重要。而 Spring Boot3 推出的全局异常处理机制,正是为了让开发人员从繁琐的异常处理中解脱出来,专注于业务逻辑的实现。
解决方案
创建全局异常处理器
首先,创建一个类,用@ControllerAdvice注解标记,它会拦截所有控制器抛出的异常。同时,使用@ExceptionHandler注解来定义要处理的异常类型。示例代码如下:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView handleAllException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("errorMessage", ex.getMessage());
return modelAndView;
}
}
在上述代码中,@ControllerAdvice注解使得GlobalExceptionHandler类成为一个全局异常处理类,它能捕获所有控制器层抛出的异常。@ExceptionHandler(Exception.class)表示该方法处理所有类型为Exception的异常。当捕获到异常时,创建一个ModelAndView对象,设置视图名为error,并将异常信息添加到模型中返回,这样前端可以根据error视图来展示相应的错误页面给用户。
针对特定异常进行处理
有时候,我们需要对不同类型的异常做出不同响应。例如,当用户输入非法参数时,Spring Boot 会抛出
MethodArgumentNotValidException异常,我们可以单独创建一个处理该异常的方法:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
FieldError fieldError = ex.getBindingResult().getFieldError();
String errorMessage = fieldError.getDefaultMessage();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMessage);
}
}
这段代码中,@ExceptionHandler(
MethodArgumentNotValidException.class)指定该方法专门处理
MethodArgumentNotValidException异常。通过ex.getBindingResult().getFieldError()获取到具体的字段错误信息,然后将错误信息封装在ResponseEntity中,以 HTTP 状态码 400(BAD_REQUEST)返回给前端,这样前端可以根据错误信息提示用户修改输入参数。
再比如,当遇到权限不足的情况,可能会抛出AccessDeniedException异常,我们也可以单独处理:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<String> handleAccessDeniedException(AccessDeniedException ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("权限不足,无法访问该资源");
}
}
这里,捕获到AccessDeniedException异常后,返回 HTTP 状态码 403(FORBIDDEN)以及相应的错误提示信息给前端,告知用户权限不足。
自定义异常和处理
在实际开发中,我们还可以创建自定义异常,增强程序的可读性和维护性。比如,创建一个BusinessException:
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
BusinessException继承自RuntimeException,方便在业务逻辑中抛出。例如在一个电商系统中,当用户试图购买超出限购数量的商品时,可以抛出BusinessException:
public void purchaseProduct(User user, Product product, int quantity) {
if (quantity > product.getLimitPurchaseQuantity()) {
throw new BusinessException("购买数量超出限购数量");
}
// 正常购买逻辑
}
然后,在全局异常处理器中添加对BusinessException的处理:
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 {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<String> handleBusinessException(BusinessException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
}
}
在上述代码中,当捕获到BusinessException异常时,以 HTTP 状态码 500(INTERNAL_SERVER_ERROR)返回异常信息给前端。不过在实际应用中,也可以根据业务需求调整 HTTP 状态码,比如返回 400(BAD_REQUEST),表示客户端请求有误。
结合 AOP 进行更细粒度的异常处理
除了上述基于控制器的异常处理方式,我们还可以结合 AOP(面向切面编程)进行更细粒度的异常处理。例如,在一些特定的业务方法执行前后,我们希望统一处理可能出现的异常,同时记录日志等操作。
首先,引入 AOP 相关依赖:
<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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BusinessAspect {
private static final Logger logger = LoggerFactory.getLogger(BusinessAspect.class);
@Around("execution(* com.example.yourpackage.*Service.*(..))")
public Object aroundBusinessMethod(ProceedingJoinPoint joinPoint) {
try {
logger.info("开始执行方法: {}", joinPoint.getSignature().getName());
return joinPoint.proceed();
} catch (Throwable throwable) {
logger.error("方法执行出错", throwable);
// 可以根据异常类型进行不同处理,这里简单示例返回一个默认值
if (throwable instanceof BusinessException) {
return "业务处理失败";
} else {
return "系统异常";
}
}
}
}
在上述代码中,@Aspect注解标识该类为一个切面类,@Component注解将其纳入 Spring 容器管理。@Around("execution(* com.example.yourpackage.*Service.*(..))")表示对com.example.yourpackage包下所有Service类的所有方法进行环绕增强。在方法执行前记录日志,方法执行过程中捕获异常,记录异常日志,并根据异常类型进行不同处理。这样可以在不修改业务方法代码的前提下,实现对业务方法异常的统一处理和日志记录。
异常处理与错误码、错误信息的统一管理
在实际项目中,为了更好地与前端交互以及进行系统维护,我们通常需要统一管理异常对应的错误码和错误信息。可以创建一个枚举类来定义错误码和错误信息:
public enum ErrorCode {
SUCCESS(0, "成功"),
PARAMETER_ERROR(1001, "参数错误"),
ACCESS_DENIED(1002, "权限不足"),
BUSINESS_ERROR(1003, "业务处理失败"),
SYSTEM_ERROR(5000, "系统内部错误");
private int code;
private String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
然后在全局异常处理器中,根据不同的异常类型返回对应的错误码和错误信息:
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 {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
ErrorCode errorCode = ErrorCode.PARAMETER_ERROR;
String errorMessage = ex.getBindingResult().getFieldError().getDefaultMessage();
ErrorResponse errorResponse = new ErrorResponse(errorCode.getCode(), errorMessage);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException ex) {
ErrorCode errorCode = ErrorCode.ACCESS_DENIED;
ErrorResponse errorResponse = new ErrorResponse(errorCode.getCode(), errorCode.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
ErrorCode errorCode = ErrorCode.BUSINESS_ERROR;
ErrorResponse errorResponse = new ErrorResponse(errorCode.getCode(), ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllException(Exception ex) {
ErrorCode errorCode = ErrorCode.SYSTEM_ERROR;
ErrorResponse errorResponse = new ErrorResponse(errorCode.getCode(), ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
class ErrorResponse {
private int code;
private String message;
public ErrorResponse(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
在上述代码中,ErrorResponse类用于封装错误码和错误信息。在各个异常处理方法中,根据异常类型获取对应的ErrorCode,并构建ErrorResponse对象返回给前端。这样前端可以根据错误码进行更准确的错误提示和业务逻辑处理。
总结
通过 Spring Boot3 的全局异常处理机制,结合自定义异常、AOP 以及错误码与错误信息的统一管理,我们能够统一、高效地管理项目中的异常,让代码更加简洁、清晰,提升系统的稳定性和可维护性。