Spring Boot中的统一异常处理:ErrorController与@ControllerAdvice
你好,Spring Boot的“急救医生”
大家好!今天我们要聊的是Spring Boot中非常重要的两个概念:ErrorController
和@ControllerAdvice
。它们就像是Spring Boot应用中的“急救医生”,专门负责处理那些意外的错误,确保我们的应用程序在遇到问题时能够优雅地响应,而不是直接崩溃或者返回一个难看的500错误页面。
1. 为什么需要统一异常处理?
想象一下,你正在开发一个电商网站,用户可以浏览商品、加入购物车、下单支付。突然,某个地方抛出了一个NullPointerException
,导致整个页面显示了一个丑陋的500错误。用户看到这个错误后,可能会感到困惑甚至失去信心,进而放弃购买。这显然不是一个好的用户体验。
为了避免这种情况,我们需要一种机制来捕获所有的异常,并且根据不同的异常类型返回合适的响应。这就是统一异常处理的作用。通过统一异常处理,我们可以:
- 捕获所有未处理的异常
- 根据异常类型返回不同的HTTP状态码
- 返回友好的错误信息给用户
- 记录日志以便后续排查问题
2. ErrorController:全局错误处理的第一道防线
ErrorController
是Spring Boot提供的一个接口,用于处理全局的错误。它是Spring Boot默认的错误处理机制之一。当你访问一个不存在的URL时,Spring Boot会自动调用ErrorController
来处理404错误;当发生内部服务器错误时,它也会调用ErrorController
来处理500错误。
默认实现:BasicErrorController
Spring Boot自带了一个默认的ErrorController
实现,叫做BasicErrorController
。它会根据请求的内容类型(如JSON或HTML)返回相应的错误响应。例如,如果你发送的是一个JSON请求,BasicErrorController
会返回一个JSON格式的错误信息;如果是HTML请求,则会返回一个简单的错误页面。
public class BasicErrorController implements ErrorController {
@RequestMapping(produces = "application/json")
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
@RequestMapping
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
return new ModelAndView("error", model);
}
}
自定义ErrorController
虽然BasicErrorController
已经足够强大,但有时候我们可能需要更细粒度的控制。比如,我们想要自定义错误页面的样式,或者在某些情况下返回特定的HTTP状态码。这时,我们可以通过实现ErrorController
接口来自定义错误处理逻辑。
@Controller
public class CustomErrorController implements ErrorController {
private static final String PATH = "/error";
@RequestMapping(value = PATH)
public String errorPage(HttpServletRequest request) {
// 获取HTTP状态码
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
// 根据状态码返回不同的页面
switch (statusCode) {
case 404:
return "error/404";
case 500:
return "error/500";
default:
return "error/default";
}
}
@Override
public String getErrorPath() {
return PATH;
}
}
在这个例子中,我们根据HTTP状态码返回了不同的错误页面。这样,用户在遇到404或500错误时,会看到更加友好的提示信息,而不是一个空白的500页面。
3. @ControllerAdvice:局部异常处理的“救火队员”
@ControllerAdvice
是一个注解,用于定义全局的异常处理器。它可以捕获所有控制器中的异常,并根据异常类型进行处理。相比于ErrorController
,@ControllerAdvice
更加灵活,因为它可以针对具体的异常类型提供不同的处理逻辑。
基本用法
假设我们在一个RESTful API中,有一个控制器方法用于获取用户信息。如果用户不存在,我们应该返回一个404错误;如果数据库连接失败,我们应该返回一个500错误。我们可以使用@ControllerAdvice
来捕获这些异常,并返回相应的HTTP状态码和错误信息。
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 模拟用户不存在的情况
if (id == 1) {
throw new UserNotFoundException("User with ID 1 not found");
}
return new User(id, "John Doe");
}
}
接下来,我们定义一个全局异常处理器,使用@ControllerAdvice
注解来捕获UserNotFoundException
和其他异常。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred");
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
在这个例子中,GlobalExceptionHandler
类使用了@ControllerAdvice
注解,表示它是一个全局异常处理器。我们定义了两个异常处理方法:
handleUserNotFoundException
:专门处理UserNotFoundException
,返回404状态码和自定义的错误信息。handleGeneralException
:处理所有其他类型的异常,返回500状态码和通用的错误信息。
ErrorResponse类
为了让错误信息更加结构化,我们可以定义一个ErrorResponse
类,用于封装错误代码和错误消息。
public class ErrorResponse {
private int code;
private String message;
public ErrorResponse(int code, String message) {
this.code = code;
this.message = message;
}
// Getters and Setters
}
更多高级用法
@ControllerAdvice
不仅可以捕获异常,还可以用于全局的数据绑定验证、模型属性添加等操作。例如,我们可以在@ControllerAdvice
中定义一个方法,用于处理所有控制器中的参数验证错误。
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), String.join(", ", errors));
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
这个方法会捕获所有由@Valid
或@Validated
注解引发的验证错误,并返回一个包含所有字段验证错误的JSON响应。
4. ErrorController vs @ControllerAdvice:谁更适合你?
现在我们已经了解了ErrorController
和@ControllerAdvice
的基本用法,那么它们之间有什么区别呢?什么时候应该使用哪一个呢?让我们通过一个表格来对比它们的功能和适用场景。
特性 | ErrorController | @ControllerAdvice |
---|---|---|
作用范围 | 全局错误处理,主要处理HTTP状态码 | 局部异常处理,主要用于捕获控制器中的异常 |
触发条件 | 当Spring Boot检测到错误时自动调用 | 当控制器方法抛出异常时调用 |
返回内容 | 可以返回HTML页面或JSON响应 | 主要返回JSON响应,适合API开发 |
灵活性 | 适用于全局错误页面的定制 | 适用于细粒度的异常处理和验证错误处理 |
适用场景 | 404、500等常见HTTP错误的统一处理 | 自定义业务逻辑异常的处理,如用户不存在 |
从表格中可以看出,ErrorController
更适合处理全局的HTTP错误,而@ControllerAdvice
则更适合处理业务逻辑中的异常。在实际开发中,我们通常会结合使用两者,以达到最佳的效果。
5. 总结
今天我们一起探讨了Spring Boot中的两种异常处理机制:ErrorController
和@ControllerAdvice
。ErrorController
可以帮助我们处理全局的HTTP错误,而@ControllerAdvice
则提供了更加灵活的异常处理方式,适用于业务逻辑中的各种异常情况。
通过合理使用这两种工具,我们可以让我们的应用程序在遇到错误时更加优雅地响应,提升用户体验的同时,也方便我们进行后续的错误排查和修复。
希望这篇文章对你有所帮助!如果你有任何问题,欢迎在评论区留言,我会尽力为你解答。 😊
参考资料:
- Spring Boot官方文档
- Spring Framework官方文档
- Stack Overflow上的相关讨论
(本文不包含外部链接,所有引用的技术文档均为Spring官方文档或社区讨论中的常见内容。)