基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Spring MVC 中如何进行全局异常处理?

知识点图片

在 Spring MVC(以及 Spring Boot)中,进行全局异常处理最推荐、最常用的方式是使用 @ControllerAdvice@RestControllerAdvice 配合 @ExceptionHandler 注解。

这种方式基于 AOP(面向切面编程)思想,能够将异常处理逻辑与业务逻辑解耦,代码极其整洁。

以下是几种常见的实现方式,按推荐程度排序:


1. 推荐方式:@RestControllerAdvice + @ExceptionHandler

这是目前开发 RESTful API 最主流的方式。@RestControllerAdvice 本质上是 @ControllerAdvice + @ResponseBody,意味着返回的数据会自动序列化为 JSON。

步骤:

  1. 定义统一返回对象(可选但推荐): 为了让前端处理方便,通常会定义一个统一的 Result 类。
  2. 定义自定义异常(可选): 用于处理业务特定的错误。
  3. 创建全局异常处理类。

代码示例:

java
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// 1. 标记这是一个全局异常处理类,且返回 JSON 格式
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 2. 处理自定义业务异常
     * 假设你有一个自定义异常叫 BusinessException
     */
    @ExceptionHandler(BusinessException.class)
    public Result<String> handleBusinessException(BusinessException e) {
        logger.warn("业务异常: {}", e.getMessage());
        // 返回自定义的错误码和信息
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 3. 处理参数校验异常 (例如使用 @Valid 时产生的 MethodArgumentNotValidException)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<String> handleValidationException(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        logger.warn("参数校验失败: {}", msg);
        return Result.error(400, msg);
    }

    /**
     * 4. 处理所有未知的系统异常 (兜底策略)
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 设置 HTTP 状态码为 500
    public Result<String> handleException(Exception e) {
        logger.error("系统内部异常", e);
        return Result.error(500, "系统繁忙,请稍后再试");
    }
}

优点:

  • 解耦: 异常处理逻辑完全独立于 Controller。
  • 灵活: 可以针对不同类型的异常编写不同的处理逻辑。
  • 全局: 对所有 Controller 生效。

2. 传统方式:实现 HandlerExceptionResolver 接口

这是 Spring MVC 较底层的异常处理方式。你需要实现 resolveException 方法。

代码示例:

java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, 
                                         HttpServletResponse response, 
                                         Object handler, 
                                         Exception ex) {
        
        // 这里可以根据异常类型做判断
        ModelAndView mv = new ModelAndView();
        
        // 如果是前后端不分离项目,可以跳转到错误页面
        mv.setViewName("error/500"); 
        mv.addObject("errorMsg", ex.getMessage());
        
        // 如果是 JSON 项目,需要手动通过 response 写出 JSON,比较麻烦
        /*
        try {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":500, \"msg\":\"error\"}");
        } catch (IOException e) { ... }
        */
        
        return mv;
    }
}

缺点:

  • 需要手动处理 Response 响应流(如果是 JSON 格式),代码比较繁琐。
  • 不如注解方式直观。

3. 继承方式:BaseController (不推荐)

创建一个基类 BaseController,在其中写 @ExceptionHandler,然后让所有业务 Controller 继承它。

java
public class BaseController {
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        return Result.error(500, e.getMessage());
    }
}

// 业务 Controller
@RestController
public class UserController extends BaseController {
    // ...
}

缺点:

  • 代码侵入性强: Java 是单继承,如果 Controller 需要继承其他类就麻烦了。
  • 耦合度高: 强迫所有 Controller 继承基类。

总结与最佳实践

  1. 首选方案: 使用 @RestControllerAdvice (如果是传统页面开发则用 @ControllerAdvice) 配合 @ExceptionHandler
  2. 异常分类:
    • 自定义异常(如 UserNotFoundException):返回明确的业务错误码。
    • 参数校验异常MethodArgumentNotValidException):提取具体的校验错误信息返回给前端。
    • 兜底异常Exception.class):捕获所有未处理异常,防止堆栈信息直接暴露给前端,通常返回 "系统繁忙"。
  3. 日志记录: 在全局异常处理器中,务必打印日志(logger.error),这对排查生产环境问题至关重要。
00:00
00:00