@ControllerAdvice
是Spring 框架中的注解,多用在Spring MVC应用程序中。
用于定义全局的异常处理
和数据绑定
等跨控制器(Controller
)的增强逻辑。
@ControllerAdvice
标注在类上,表明此类包含的方法 将应用于所有的@Controller
、@RestController
及其子注解
所标注的类。
使用样例
使用场景1:全局异常处理:
# 示例1
import org.apache.ibatis.javassist.NotFoundException;
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.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<String> handleException(Exception ex) {
System.err.println("***GlobalExceptionHandler.handleException:" + ex.getMessage() + "***");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred");
}
@ExceptionHandler(value = {NotFoundException.class})
public ResponseEntity<String> handleNotFoundException(NotFoundException ex) {
System.err.println("***GlobalExceptionHandler.handleNotFoundException***");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Resource not found");
}
@ExceptionHandler(value = {NullPointerException.class, IllegalArgumentException.class})
public ResponseEntity<String> handleException(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}
}
使用场景2:数据绑定
通过实现接口ResponseBodyAdvice
来修改返回值,并直接作为 ResponseBody类型处理器 的返回值。
# 示例2
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Map;
@ControllerAdvice
public class HelloResponseBodyAdvice implements ResponseBodyAdvice<Map<String, Object>> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Map<String, Object> beforeBodyWrite(Map<String, Object> body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 修改返回值
System.out.println("origin map: " + body);
body.put("msg", "hello");
return body;
}
}
# 示例3
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import static java.util.Objects.nonNull;
@ControllerAdvice
public class HttpResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class clazz,
ServerHttpRequest request, ServerHttpResponse response) {
HttpHeaders headers = response.getHeaders();
// 分页信息添加到ServerHttpResponse
HttpHeaders headersContext = ResponseUtils.getHeaders();
if (nonNull(headersContext) && !headersContext.isEmpty()) {
headers.addAll(headersContext);
}
// 状态码添加到ServerHttpResponse
if (nonNull(ResponseUtils.getResponseCode())) {
response.setStatusCode(ResponseUtils.getResponseCode());
}
return body;
}
}
验证
CASE1:只有1个 HelloResponseBodyAdvice
时
@RestController
@RequestMapping("/test")
public class ResponseBodyAdviceController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public Map<String, Object> hello() {
Map<String, Object> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
return map;
}
}
结论: 返回值被 成功修改
CASE2:有2个 ResponseBodyAdvice
时
结论:均执行,按照类名字母降序的顺序执行,比如
HelloResponseBodyAdvice
比 PracResponseBodyAdvice
先执行,
APracResponseBodyAdvice
比 HelloResponseBodyAdvice
先执行。
CASE3:发生异常时
@RestController
@RequestMapping("/test")
public class ResponseBodyAdviceController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public Map<String, Object> hello() {
int a = 0;
int b = 5;
int i = b / a;
return null;
}
}
结论:执行顺序为:GlobalExceptionHandler
→ HelloResponseBodyAdvice
→ HttpResponseBodyAdvice
结论:异常也被修改了值,所以这个修改值的操作,慎用!!!
结论:一般微服务只有 2 个 @ControllerAdvice
,一个用作修改 HttpHeader
和 HttpStatus
(示例3
),一个用作处理异常(示例1
)。
CASE4:HandlerInterceptor
VS ResponseBodyAdvice
结论:执行顺序为 ResponseBodyAdvice
→ HandlerInterceptor
***ResponsePostInterceptor.preHandle***
***ResponsePostAdvice.supports***
***ResponsePostAdvice.beforeBodyWrite***
***ResponsePostInterceptor.postHandle***
***ResponsePostInterceptor.afterCompletion***
CASE5:@RestController
VS @Controller
结论:@RestController
会成功返回结果, @Controller
不会返回结果