SpringBoot 全局异常处理:从入门到实战

一、为什么需要全局异常处理?

在Web应用开发中,异常处理是保证系统健壮性的关键环节。传统的try-catch方式虽然直接,但存在诸多问题:

  • 代码冗余:每个Controller方法都需要重复的异常捕获逻辑
  • 维护困难:异常处理逻辑分散,修改成本高
  • 响应不统一:不同方法返回的异常格式各异,前端难以统一处理
  • 业务代码污染:异常处理与业务逻辑耦合,可读性差

SpringBoot通过全局异常处理机制,让我们能够集中管理异常,统一返回格式,提升开发效率和系统稳定性。

二、SpringBoot全局异常处理核心组件

1. @ControllerAdvice 注解

这是全局异常处理的基石。通过@ControllerAdvice标记的类,可以拦截所有Controller抛出的异常。

@ControllerAdvice
public class GlobalExceptionHandler {
    // 异常处理方法
}

@ControllerAdvice支持更细粒度的控制:

// 只处理指定包下的Controller
@ControllerAdvice(basePackages = "com.example.controller")

// 只处理指定注解标记的Controller
@ControllerAdvice(annotations = RestController.class)

// 只处理指定类
@ControllerAdvice(assignableTypes = {UserController.class, OrderController.class})

2. @ExceptionHandler 注解

用于标记具体的异常处理方法,可以针对特定异常类型进行捕获:

@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleException(Exception ex) {
    // 处理逻辑
}

3. @ResponseStatus 注解

可以配合@ExceptionHandler使用,指定HTTP响应状态码:

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
    return new ErrorResponse("资源不存在", ex.getMessage());
}

三、实战:构建完整的全局异常处理体系

1. 定义统一响应体

首先创建标准的错误响应格式:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp = System.currentTimeMillis();

    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data, System.currentTimeMillis());
    }

    public static ApiResponse<?> error(Integer code, String message) {
        return new ApiResponse<>(code, message, null, System.currentTimeMillis());
    }
}

2. 自定义业务异常

创建业务异常基类,便于统一处理:

public class BusinessException extends RuntimeException {
    private Integer code;

    public BusinessException(String message) {
        super(message);
        this.code = 500;
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    // getter方法
}

3. 实现全局异常处理器

核心的异常处理类:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ApiResponse<?> handleBusinessException(BusinessException ex) {
        log.error("业务异常: {}", ex.getMessage(), ex);
        return ApiResponse.error(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        String message = ex.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining("; "));
        log.error("参数校验异常: {}", message);
        return ApiResponse.error(400, message);
    }

    /**
     * 处理数据绑定异常
     */
    @ExceptionHandler(BindException.class)
    public ApiResponse<?> handleBindException(BindException ex) {
        String message = ex.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining("; "));
        log.error("数据绑定异常: {}", message);
        return ApiResponse.error(400, message);
    }

    /**
     * 处理其他运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ApiResponse<?> handleRuntimeException(RuntimeException ex) {
        log.error("运行时异常: {}", ex.getMessage(), ex);
        return ApiResponse.error(500, "系统繁忙,请稍后重试");
    }

    /**
     * 处理其他所有异常
     */
    @ExceptionHandler(Exception.class)
    public ApiResponse<?> handleException(Exception ex) {
        log.error("系统异常: {}", ex.getMessage(), ex);
        return ApiResponse.error(500, "系统异常");
    }
}

4. 使用示例

在Controller中抛出异常:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public ApiResponse<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        if (user == null) {
            throw new BusinessException(404, "用户不存在");
        }
        return ApiResponse.success(user);
    }

    @PostMapping
    public ApiResponse<?> createUser(@Valid @RequestBody UserDTO userDTO) {
        // 参数校验由@Valid自动完成,失败会抛出MethodArgumentNotValidException
        userService.create(userDTO);
        return ApiResponse.success(null);
    }
}

四、进阶技巧与最佳实践

1. 异常日志分级处理

不同级别的异常采用不同的日志级别:

@ExceptionHandler(BusinessException.class)
public ApiResponse<?> handleBusinessException(BusinessException ex) {
    if (ex.getCode() >= 500) {
        log.error("业务异常: {}", ex.getMessage(), ex);
    } else {
        log.warn("业务异常: {}", ex.getMessage());
    }
    return ApiResponse.error(ex.getCode(), ex.getMessage());
}

2. 国际化支持

结合Spring的MessageSource实现异常消息国际化:

@Autowired
private MessageSource messageSource;

@ExceptionHandler(BusinessException.class)
public ApiResponse<?> handleBusinessException(BusinessException ex, HttpServletRequest request) {
    String message = messageSource.getMessage(ex.getMessage(), null, 
            LocaleContextHolder.getLocale());
    return ApiResponse.error(ex.getCode(), message);
}

3. 自定义异常处理顺序

当多个异常处理器都能处理同一异常时,可以通过@Order注解或实现Ordered接口控制优先级:

@Order(Ordered.HIGHEST_PRECEDENCE)
@ExceptionHandler(BusinessException.class)
public ApiResponse<?> handleBusinessException(BusinessException ex) {
    // 高优先级处理
}

4. 处理特定HTTP状态码异常

Spring MVC会抛出特定状态码的异常,如HttpRequestMethodNotSupportedException(405),可以单独处理:

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ApiResponse<?> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex) {
    return ApiResponse.error(405, "请求方法不支持");
}

五、常见问题与解决方案

1. 异常处理器不生效?

  • 检查@ControllerAdvice@RestControllerAdvice是否被Spring扫描到
  • 确认异常类型是否匹配@ExceptionHandler指定的类型
  • 检查是否有更高优先级的异常处理器已经处理了该异常

2. 如何测试全局异常处理?

使用MockMvc进行单元测试:

@SpringBootTest
@AutoConfigureMockMvc
class GlobalExceptionHandlerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testBusinessException() throws Exception {
        mockMvc.perform(get("/api/users/999"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(404))
                .andExpect(jsonPath("$.message").value("用户不存在"));
    }
}

3. 如何处理Filter中的异常?

Filter中的异常无法被@ControllerAdvice捕获,可以通过FilterdoFilter方法捕获,然后重定向到错误处理接口:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    try {
        chain.doFilter(request, response);
    } catch (Exception ex) {
        // 重定向到错误处理接口
        ((HttpServletResponse) response).sendRedirect("/error");
    }
}

或者使用OncePerRequestFiltershouldNotFilterErrorDispatch方法。

六、总结

SpringBoot的全局异常处理机制通过@ControllerAdvice@ExceptionHandler提供了优雅的解决方案。通过本文的实践,你可以:

  • ✅ 统一异常响应格式,提升前后端协作效率
  • ✅ 集中管理异常处理逻辑,降低维护成本
  • ✅ 实现异常日志分级,便于问题排查
  • ✅ 支持国际化,满足多语言需求
  • ✅ 处理各种类型的异常,包括参数校验、业务异常等

建议在实际项目中根据业务需求进行定制化扩展,如添加请求ID追踪、异常监控上报等功能,构建更加健壮的后端服务。


代码示例说明:以上代码基于SpringBoot 2.x/3.x版本,需要引入Lombok、Spring Web等依赖。实际使用时请根据项目结构进行调整。


作 者:南烛
链 接:https://www.itnotes.top/archives/1405
来 源:IT笔记
文章版权归作者所有,转载请注明出处!


上一篇