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
有了更深入的了解。这两种工具可以帮助我们构建一个健壮的异常处理机制,提升用户体验的同时,也方便我们进行调试和维护。希望大家在今后的开发中能够灵活运用这些工具,写出更加优雅的代码!
如果有任何问题,欢迎在评论区留言,我们下期再见! ?