顾乔芝士网

持续更新的前后端开发技术栈

Spring Boot3 全局异常处理:难题破解与实操指南

你在使用 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 以及错误码与错误信息的统一管理,我们能够统一、高效地管理项目中的异常,让代码更加简洁、清晰,提升系统的稳定性和可维护性。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言