以下是我目前面临的情况的一些事实
-
我最近构建了一个具有各种
ExceptionHandler
的RestControllerAdvice
作为我的 Spring RestController 的全局异常处理程序。 -
由于我想返回我的自定义响应 json 来处理
ResponseEntityExceptionHandler
中指定的预定义 HTTP 错误,我的RestControllerAdvice
类继承了ResponseEntityExceptionHandler
和方法,如handleHttpRequestMethodNotSupported()
,handleHttpMessageNotReadable()
被覆盖。 -
我已经成功地覆盖了
handleHttpMediaTypeNotSupported()
和handleHttpMessageNotReadable()
但是当涉及到handleHttpRequestMethodNotSupported()
时,我未能做到这一点。
这是我的代码摘录:
@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice(annotations=RestController.class)
public class TestRestExceptionHandler extends ResponseEntityExceptionHandler{
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Request Method Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Message Not Readable");
return handleExceptionInternal(ex, response, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Media Type Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
}
handleHttpRequestMethodNotSupported()
日志如下所示:
[2019-06-05T17:49:50.368+0800][XNIO-74 task-7][WARN ][o.s.w.s.m.s.DefaultHandlerExceptionResolver] Resolved exception caused by Handler execution: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
handleHttpMessageNotReadable()
日志如下所示:
[2019-06-05T17:50:21.915+0800][XNIO-74 task-8][WARN ][o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver] Resolved exception caused by Handler execution
如您所见,成功的代码由ExceptionHandlerExceptionResolver
处理,而故障代码由DefaultHandlerExceptionResolver
处理。
我想知道根本原因是什么,如果有人可以推荐任何可用的解决方案,我将不胜感激。谢谢。
从jackycflau的答案中,我们可以总结为2个问题。
问题 1.为什么删除
annotations=RestController.class
适用于 HttpRequestMethodNotSupportedException问题 2.为什么只有
HttpRequestMethodNotSupportedException
没有被抓住?
要回答这两个问题,我们需要查看有关 spring 如何处理异常的代码。以下源代码基于 spring 4.3.5。
在 SpringDispatcherServlet
处理请求期间,当发生错误时,HandlerExceptionResolver
将尝试解决异常。在给定的情况下,例外将委派给ExceptionHandlerExceptionResolver
。确定解决异常的方法的方法ExceptionHandlerExceptionResolver.java
(getExceptionHandlerMethod
第 417 行中)
/**
* Find an {@code @ExceptionHandler} method for the given exception. The default
* implementation searches methods in the class hierarchy of the controller first
* and if not found, it continues searching for additional {@code @ExceptionHandler}
* methods assuming some {@linkplain ControllerAdvice @ControllerAdvice}
* Spring-managed beans were detected.
* @param handlerMethod the method where the exception was raised (may be {@code null})
* @param exception the raised exception
* @return a method to handle the exception, or {@code null}
*/
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
if (handlerMethod != null) {
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
}
for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
}
}
}
return null;
}
由于我们正在使用@RestControllerAdvice
,我们只需要关注 for 循环,这决定了使用哪个ControllerAdviceBean
。我们可以看到,isApplicableToBeanType
的方法将判断ControllerAdviceBean
是否适用,相关代码分别为(ControllerAdviceBean.java
第 149 行)
/**
* Check whether the given bean type should be assisted by this
* {@code @ControllerAdvice} instance.
* @param beanType the type of the bean to check
* @see org.springframework.web.bind.annotation.ControllerAdvice
* @since 4.0
*/
public boolean isApplicableToBeanType(Class<?> beanType) {
if (!hasSelectors()) {
return true;
}
else if (beanType != null) {
for (String basePackage : this.basePackages) {
if (beanType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, beanType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
return true;
}
}
}
return false;
}
private boolean hasSelectors() {
return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
}
通过阅读代码,我们可以解释正在发生的事情:
问题 1 的答案
删除annotations=RestController.class
时,hasSelectors
将返回 false,因此isApplicableToBeanType
将返回 true。因此,在这种情况下,HttpRequestMethodNotSupportedException
将由TestRestExceptionHandler
处理。
问题 2 的答案
对于HttpRequestMethodNotSupportedException
,DispatcherSerlvet
找不到控制器方法来处理请求。因此handlerMethod
传递给getExceptionHandlerMethod
是null
,然后传递给isApplicableToBeanType
beanType
也是空的,并且返回假。
另一方面,DispatcherSerlvet
可以找到HttpMessageNotReadableException
或HttpMediaTypeNotSupportedException
的控制器方法。因此,其余控制器处理程序方法将传递给getExceptionHandlerMethod
并且isApplicableToBeanType
将返回 true。
我已经找到了问题的罪魁祸首,这是关于@RestControllerAdvice
注释的。
最初,我用@RestControllerAdvice(annotations=RestController.class)
注释了该类。
在我删除annotations
键值对后(即只需用@RestControllerAdvice
注释类),HttpRequestMethodNotSupportedException
现在已成功捕获。
这是我只能分享的解决方案。我不明白根本原因,这种行为对我来说似乎很奇怪......可能是因为HttpRequestMethodNotSupportedException
不受@RestController
???的控制(只是一个疯狂的猜测)。如果有人能对这种行为给出完整的解释,我会很高兴。