Spring中的统一异常处理:ErrorController与@ControllerAdvice

Spring中的统一异常处理:ErrorController与@ControllerAdvice

开场白

大家好,欢迎来到今天的Spring技术讲座。今天我们要聊的是Spring框架中非常重要的一个话题——统一异常处理。相信很多开发者在开发过程中都遇到过这样的问题:用户输入了错误的URL,或者后台抛出了一个意想不到的异常,导致页面一片空白,用户体验极差。为了解决这些问题,Spring提供了两种非常强大的工具:ErrorController@ControllerAdvice。接下来,我们就来深入探讨一下这两个工具的使用方法和最佳实践。

1. ErrorController:处理全局404、500等HTTP错误

1.1 什么是ErrorController?

ErrorController是Spring Boot提供的一个接口,用于自定义全局的HTTP错误处理逻辑。默认情况下,Spring Boot会自动处理一些常见的HTTP错误(如404 Not Found、500 Internal Server Error),并返回一个简单的HTML页面或JSON响应。但有时候,我们希望对这些错误进行更精细的控制,比如返回自定义的错误信息,或者根据不同类型的错误跳转到不同的页面。这时,ErrorController就派上用场了。

1.2 实现ErrorController

要实现ErrorController,我们需要创建一个类,并实现ErrorController接口。这个接口只有一个方法需要实现:getErrorPath()。此外,我们还需要定义一个处理错误请求的方法,通常使用@RequestMapping("/error")来捕获所有未处理的错误请求。

示例代码:

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

@Controller
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public String handleError(HttpServletRequest request, Model model) {
        // 获取错误状态码
        Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

        // 根据不同的状态码返回不同的视图
        if (statusCode == 404) {
            model.addAttribute("message", "页面不存在,请检查URL");
            return "error/404";
        } else if (statusCode == 500) {
            model.addAttribute("message", "服务器内部错误,请稍后再试");
            return "error/500";
        } else {
            model.addAttribute("message", "发生了未知错误");
            return "error/general";
        }
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }
}

1.3 ErrorAttributes:获取更多错误信息

除了简单的状态码,我们还可以通过ErrorAttributes获取更多的错误信息。ErrorAttributes是一个接口,Spring Boot默认实现了DefaultErrorAttributes,它可以帮助我们从请求中提取出更多的上下文信息,比如异常堆栈、请求路径等。

示例代码:

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);

        // 添加自定义属性
        errorAttributes.put("customMessage", "这是一个自定义的错误消息");

        return errorAttributes;
    }
}

1.4 总结

ErrorController主要用于处理全局的HTTP错误,比如404、500等。通过实现ErrorController接口,我们可以自定义错误页面或返回特定的JSON响应。同时,ErrorAttributes可以帮助我们获取更多的错误信息,以便更好地调试和处理问题。


2. @ControllerAdvice:处理控制器中的异常

2.1 什么是@ControllerAdvice?

@ControllerAdvice是Spring MVC提供的一种注解,用于全局处理控制器中的异常。与@ExceptionHandler类似,但它的作用范围是整个应用程序,而不是单个控制器。通过@ControllerAdvice,我们可以集中管理所有的异常处理逻辑,避免在每个控制器中重复编写相同的异常处理代码。

2.2 使用@ControllerAdvice

@ControllerAdvice可以与@ExceptionHandler配合使用,来捕获并处理特定类型的异常。我们可以在一个类中标记@ControllerAdvice,然后在这个类中定义多个@ExceptionHandler方法,分别处理不同类型的异常。

示例代码:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class GlobalExceptionHandler {

    // 处理IllegalArgumentException
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>("非法参数: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    // 处理NullPointerException
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<String> handleNullPointerException(NullPointerException ex) {
        return new ResponseEntity<>("空指针异常: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // 处理其他所有未捕获的异常
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<String> handleException(Exception ex) {
        return new ResponseEntity<>("服务器发生错误: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

2.3 细分异常处理

有时候,我们可能需要根据不同的业务场景,对同一类型的异常进行不同的处理。例如,某些业务场景下,IllegalArgumentException应该返回400 Bad Request,而在其他场景下,它可能应该返回422 Unprocessable Entity。为了实现这一点,我们可以在@ExceptionHandler方法中添加更多的逻辑判断。

示例代码:

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex, HttpServletRequest request) {
    // 根据请求路径判断是否是特定的业务场景
    if (request.getRequestURI().startsWith("/api/v1/user")) {
        return new ResponseEntity<>("用户模块非法参数: " + ex.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY);
    } else {
        return new ResponseEntity<>("非法参数: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

2.4 返回自定义错误对象

除了返回简单的字符串,我们还可以返回一个自定义的错误对象,包含更多的错误信息。这样可以让前端更容易解析错误,并根据错误类型做出相应的处理。

示例代码:

public class ApiError {
    private int status;
    private String message;
    private String path;

    public ApiError(int status, String message, String path) {
        this.status = status;
        this.message = message;
        this.path = path;
    }

    // Getters and Setters
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleException(Exception ex, HttpServletRequest request) {
    ApiError apiError = new ApiError(
        HttpStatus.INTERNAL_SERVER_ERROR.value(),
        ex.getMessage(),
        request.getRequestURI()
    );
    return new ResponseEntity<>(apiError, HttpStatus.INTERNAL_SERVER_ERROR);
}

2.5 总结

@ControllerAdvice是一种非常强大的工具,用于全局处理控制器中的异常。通过它,我们可以集中管理所有的异常处理逻辑,避免代码重复。同时,@ExceptionHandler允许我们针对不同类型的异常进行精细化处理,甚至可以根据业务场景返回不同的响应。


3. ErrorController与@ControllerAdvice的区别与结合使用

3.1 区别

  • ErrorController:主要用于处理全局的HTTP错误(如404、500),并且可以通过ErrorAttributes获取更多的错误信息。
  • @ControllerAdvice:主要用于处理控制器中的异常,适用于业务逻辑层面的异常处理。

3.2 结合使用

虽然ErrorController@ControllerAdvice的功能有所不同,但在实际项目中,它们可以很好地结合使用。ErrorController可以处理全局的HTTP错误,而@ControllerAdvice则可以处理具体的业务异常。通过这种方式,我们可以构建一个更加健壮的异常处理机制,确保用户在任何情况下都能获得友好的错误提示。

示例代码:

// ErrorController处理全局HTTP错误
@Controller
public class CustomErrorController implements ErrorController {
    @RequestMapping("/error")
    public String handleError(HttpServletRequest request, Model model) {
        Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        if (statusCode == 404) {
            return "error/404";
        } else if (statusCode == 500) {
            return "error/500";
        } else {
            return "error/general";
        }
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }
}

// @ControllerAdvice处理业务异常
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>("非法参数: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return new ResponseEntity<>("服务器发生错误: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

3.3 最佳实践

  • 分离职责ErrorController负责处理全局的HTTP错误,@ControllerAdvice负责处理业务异常。这样可以保持代码的清晰和可维护性。
  • 统一响应格式:无论是HTTP错误还是业务异常,尽量返回统一的错误响应格式,方便前端解析。
  • 日志记录:在异常处理过程中,建议将异常信息记录到日志中,便于后续排查问题。

结语

通过今天的讲座,相信大家对Spring中的ErrorController@ControllerAdvice有了更深入的了解。这两种工具可以帮助我们构建一个健壮的异常处理机制,提升用户体验的同时,也方便我们进行调试和维护。希望大家在今后的开发中能够灵活运用这些工具,写出更加优雅的代码!

如果有任何问题,欢迎在评论区留言,我们下期再见! ?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注