RestControllerAdvice 和 ControllerAdvice 两个注解的区别与联系
它们都用于实现全局的通用处理逻辑,主要应用在以下三个方面:
- 全局异常处理: 使用
@ExceptionHandler
注解的方法。 - 全局数据绑定: 使用
@InitBinder
注解的方法。 - 全局数据预处理: 使用
@ModelAttribute
注解的方法。
联系:
- 核心功能相同: 两者都提供了上述三种全局处理的能力。我们可以将原本分散在多个 Controller 中的通用异常处理、数据绑定规则、公共 Model 属性提取到标有这两个注解的类中统一管理。
- 都是
@Component
: 从 Spring 的角度看,它们都是@Component
的特殊化注解。这意味着 Spring 容器会自动扫描并注册这些 Bean。 @RestControllerAdvice
是@ControllerAdvice
的组合注解: 这是最关键的联系。@RestControllerAdvice
本质上是@ControllerAdvice
和@ResponseBody
两个注解的组合。
查看 @RestControllerAdvice
的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice // <--- 包含了 @ControllerAdvice
@ResponseBody // <--- 包含了 @ResponseBody
public @interface RestControllerAdvice {// ... 注解属性定义 ...
}
区别 (核心在于 @ResponseBody
):
这个组合带来的核心区别在于对包含 @ExceptionHandler
注解的方法的返回值处理方式不同:
-
@ControllerAdvice
:- 如果其内部的
@ExceptionHandler
方法没有添加@ResponseBody
注解,那么其返回值通常会被视图解析器(ViewResolver)处理。会返回一个String
类型的视图名,或者一个ModelAndView
对象,用于页面跳转或渲染。 - 如果其内部的
@ExceptionHandler
方法添加了@ResponseBody
注解,就和@RestControllerAdvice
中的方法一样了,返回值会直接写入 HTTP 响应体中,通常是 JSON 或 XML 格式。
- 如果其内部的
-
@RestControllerAdvice
:- 由于它自带了
@ResponseBody
注解,所以其内部所有@ExceptionHandler
方法(以及@ModelAttribute
方法)的返回值默认就会被 Spring MVC 当作响应体内容处理,通过HttpMessageConverter
进行序列化(例如转为 JSON)并写入 Response Body。 - 它特别适用于构建 RESTful API 的全局异常处理,因为 REST API 通常期望返回的是数据(如 JSON),而不是一个 HTML 视图。
- 由于它自带了
总结与选择:
特性 | @ControllerAdvice | @RestControllerAdvice (@ControllerAdvice + @ResponseBody ) |
---|---|---|
核心目的 | 全局 Controller 增强 (异常、数据绑定、模型属性) | 全局 Controller 增强 (异常、数据绑定、模型属性) |
基础注解 | @Component | @Component |
组合 | 无 | @ControllerAdvice + @ResponseBody |
@ExceptionHandler 返回值默认处理 | 视图解析 (返回视图名/ModelAndView) | 写入响应体 (通过 HttpMessageConverter 转为 JSON/XML 等) |
适用场景 | 1. 传统的 Spring MVC 应用 (返回 HTML 视图) 2. 需要混合处理(部分返回视图,部分加 @ResponseBody 返回 JSON)的应用3. 只用于全局 @InitBinder 或不返回数据的 @ModelAttribute | 1. RESTful API (主要返回 JSON/XML 数据) 2. 所有全局异常都希望返回 JSON/XML 错误结构体 |
简单来说:
- 如果你在构建一个传统的、主要返回 HTML 页面的 Spring MVC 应用,或者需要灵活处理部分返回视图、部分返回 JSON 的情况,使用
@ControllerAdvice
。如果某个异常处理器需要返回 JSON,可以在该方法上单独加@ResponseBody
。 - 如果你在构建一个纯粹的 RESTful API,所有的 Controller 和异常处理都期望返回 JSON 或 XML 数据,那么使用
@RestControllerAdvice
更方便,因为它省去了在每个@ExceptionHandler
方法上都写@ResponseBody
的麻烦。
示例:
使用 @ControllerAdvice
(可能返回视图)
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@ControllerAdvice
public class GlobalWebExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalWebExceptionHandler.class);@ExceptionHandler(Exception.class) // 处理所有未捕获的异常public ModelAndView handleGenericException(Exception e) {log.error("Unhandled exception occurred", e); // 建议记录完整堆栈ModelAndView mav = new ModelAndView();mav.addObject("errorMessage", "服务器内部错误,请稍后再试。");mav.setViewName("error"); // 返回名为 "error" 的视图 (e.g., error.html)return mav;}@ExceptionHandler(CustomBusinessException.class)// 如果这个特定的异常想返回JSON,需要单独加 @ResponseBody// @ResponseBodypublic ModelAndView handleBusinessException(CustomBusinessException e) {log.warn("Business exception: {}", e.getMessage());ModelAndView mav = new ModelAndView();mav.addObject("errorMessage", e.getMessage());mav.setViewName("business-error");return mav;}
}
使用 @RestControllerAdvice
(返回 JSON)
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;// 注意这里是 @RestControllerAdvice
@RestControllerAdvice
public class GlobalApiExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalApiExceptionHandler.class);// 返回值会自动通过 HttpMessageConverter 转为 JSON (或其他协商格式)@ExceptionHandler(Exception.class)public ResponseEntity<ErrorResponse> handleGenericApiException(Exception e) {log.error("Unhandled API exception occurred", e);ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error", "An unexpected error occurred.");return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);}@ExceptionHandler(ResourceNotFoundException.class)public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException e) {log.warn("Resource not found: {}", e.getMessage());ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), "Not Found", e.getMessage());return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);}// 简单的错误响应体类public static class ErrorResponse {private int status;private String error;private String message;public ErrorResponse(int status, String error, String message) {this.status = status;this.error = error;this.message = message;}// Getters...public int getStatus() { return status; }public String getError() { return error; }public String getMessage() { return message; }}
}// 假设的自定义异常
class ResourceNotFoundException extends RuntimeException {public ResourceNotFoundException(String message) { super(message); }
}
class CustomBusinessException extends RuntimeException {public CustomBusinessException(String message) { super(message); }
}