Spring Boot 3.2.x / Spring Web 6.1.x
在同时具有 MVC 控制器(通过 JSP 返回 HTML 页面)和 REST 控制器的应用程序中,如何实现全局异常处理,以便 MVC 控制器方法导致错误page,而 REST 端点返回非页面响应(400、404) 、500 等,带有文本或 JSON 响应正文)?
我实现了一个
@ControllerAdvice
,可以使用特定的 @ExceptionHandler
和 @ResponseStatus
注释来处理各种异常。这些方法都返回一个页面(例如,"forward:/error"
),因为它们应该用于 MVC 请求。
这是该课程的一部分:
@ControllerAdvice
public class GlobalDefaultExceptionHandler {
//...other methods...
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String defaultInternalErrorHandler(Model model, Exception e, HttpServletRequest req) {
UUID errorID = UUID.randomUUID();
log.error(errorID + " | Unhandled exception processing request to " + req.getRequestURI(), e);
model.addAttribute("exception", e);
model.addAttribute("errorID", errorID);
return "forward:/error";
}
}
如果其他方法都没有处理抛出的异常,则这是“后备”处理程序方法。这对于返回页面 (HTML) 的所有 MVC 控制器方法都非常有用。
但是,我不希望 REST 控制器端点返回 HTML 内容,它们应该返回(在我的例子中为 JSON)具有适当 HTTP 响应状态的内容(例如,无效请求为 400,服务器错误为 500 等)。
我尝试添加另一个与此类似的
@RestControllerAdvice
类:
@RestControllerAdvice
@Slf4j
public class GlobalRestExceptionHandler {
@ExceptionHandler(ServletRequestBindingException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handle(ServletRequestBindingException e, HttpServletRequest req) {
defaultInternalErrorHandler(e, req);
return "Invalid Request: " + e.getMessage();
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String defaultInternalErrorHandler(Exception e, HttpServletRequest req) {
UUID errorID = UUID.randomUUID();
log.error(errorID + " | Unhandled exception processing API request to " + req.getRequestURI(), e);
return "Server error: " + e;
}
}
不幸的是,那个类似乎没有被使用。来自 REST 端点的任何异常都会流经
@ControllerAdvice
默认方法并返回错误 page。
如何才能两全其美:MVC 请求转到错误页面,REST 端点返回 REST 友好 (JSON) 内容?
这是由建议接口本身支持的,并且它们各自提供了两种实现此目的的方法。
您可以通过它们所在的包或控制器注释来区分两者。
因此,如果您的类由
com.myapp.controller.web
和 com.myapp.controller.rest
等包分隔,您可以分别使用 @ControllerAdvice("com.myapp.controller.web")
和 @RestControllerAdvice("com.myapp.controller.rest")
。
如果您想让注释来驱动它,您只需使用
@ControllerAdvice(annotations = Controller.class)
或 @RestControllerAdvice(annotations = RestController.class)