Spring:RestController 和 Controller 的不同异常处理程序
Posted
技术标签:
【中文标题】Spring:RestController 和 Controller 的不同异常处理程序【英文标题】:Spring: Different exception handler for RestController and Controller 【发布时间】:2017-09-05 15:30:01 【问题描述】:在 Spring 中,您可以通过 @ControllerAdvice 和 @ExceptionHandler 注解设置“全局”异常处理程序。我正在尝试利用这种机制来拥有两个全局异常处理程序:
RestControllerExceptionHandler
- 对于带有 @RestController
注释的任何控制器,它都应该以 json 格式返回错误响应
ControllerExceptionHandler
- 应该将错误消息打印到任何其他控制器的屏幕上(带有 @Controller
注释)
问题是当我声明这两个异常处理程序时,spring总是使用ControllerExceptionHandler
而不是RestControllerExceptionHandler
来处理异常。
如何使这项工作? 顺便说一句:我尝试使用 @Order 注释,但这似乎不起作用。
这是我的异常处理程序:
// should handle all exception for classes annotated with
@ControllerAdvice(annotations = RestController.class)
public class RestControllerExceptionHandler
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e)
// below object should be serialized to json
ErrorResponse errorResponse = new ErrorResponse("asdasd");
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
// should handle exceptions for all the other controllers
@ControllerAdvice(annotations = Controller.class)
public class ControllerExceptionHandler
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleUnexpectedException(Exception e)
return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR);
当我删除ControllerExceptionHandler
时,spring 正确调用了RestControllerExceptionHandler
(仅适用于带有@RestController
注释的类)....但是当我添加ControllerExceptionHandler
时,所有内容都通过ControllerExceptionHandler
进行。为什么?
【问题讨论】:
This article 描述了类似的概念并没有为默认控制器建议定义任何参数(即其中没有annotations
属性)。如果可行,你试过了吗?
@M.Prokhorov:这正是我想要实现的目标。当我看这篇文章时,我也在做同样的事情。但对我来说,由于某种原因这不起作用:/
@M.Prokhorov:我想通了。你的文章有助于深入研究这个问题。我在春天遇到了一个错误(请参阅我自己的答案)。再次感谢。
【参考方案1】:
经过一些更深入的调查,似乎字母顺序很重要:/。
当我将RestControllerExceptionHandler
重命名为ARestControllerExceptionHandler
(按字母顺序在ControllerExceptionHandler
之前)时,一切都按预期工作! ARestControllerExceptionHandler
正确处理来自RestControllers
的异常,ControllerExceptionHandler
处理来自其他控制器的异常。
我在春季为此创建了一个错误:https://jira.spring.io/browse/SPR-15432
--- 编辑:
我收到了 SPR-15432 的答案,建议可以使用 @Order
(org.springframework.core.annotation.Order
) 注释或实现 Ordered 接口来解决此问题。
这对我之前不起作用,但似乎我导入了错误的@Order
注释。(来自 log4j2 而不是 spring)。修复此问题后,它可以工作。固定版本如下:
// should handle all exception for classes annotated with
@ControllerAdvice(annotations = RestController.class)
@Order(1) // NOTE: order 1 here
public class RestControllerExceptionHandler
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e)
// below object should be serialized to json
ErrorResponse errorResponse = new ErrorResponse("asdasd");
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
// should handle exceptions for all the other controllers
@ControllerAdvice(annotations = Controller.class)
@Order(2) // NOTE: order 2 here
public class ControllerExceptionHandler
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleUnexpectedException(Exception e)
return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR);
【讨论】:
这可能确实是一个错误,但也可能不是,它可能只是 Spring 反射设施的一个实现细节,在您的情况下不保证修复。尝试考虑订购您的建议,这可以按照this answer 中的建议进行。【参考方案2】:发生这种情况是因为 @RestController 注释本身也是一个 @Controller,因此 Spring 正在考虑将 @ControllerAdvice 和 annotation = Controller.class
用于两者。
您可以尝试另一个method 来定义@ControllerAdvice 将生效的控制器子集,所以这里有一些解决方案:
解决方案 1
创建一个新的注解@NotRestController:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotRestController
用@Controller 和@NotRestController 标记不是@RestController 的控制器:
@Controller
@NotRestController
@RequestMapping("/controller")
public class SampleController
在ControllerExceptionHandler
上使用NotRestController.class
:
@ControllerAdvice(annotations = NotRestController.class)
public class ControllerExceptionHandler
我用Solution 1
创建了一个示例项目并在 Github 上分享:
https://github.com/brunocleite/springexceptionhandlertest
解决方案 2
将您的@RestController 类移动到包foo.bar
中,将您的@Controller 类移动到另一个包foo.nuk
。
使用@ControllerAdvice
的basePackages
属性而不是annotations
:
@ControllerAdvice(basePackages="foo.bar")
public class RestControllerExceptionHandler
@ControllerAdvice(basePackages="foo.nuk")
public class ControllerExceptionHandler
最好的问候!
【讨论】:
+1,但是,我知道@Controller 建议的其他选项(如解决方案 1 中所述),但我不想使用它们,因为我想利用现有的 RestController 注释。解决方案1还可以,但是我不想添加额外的不必要的注释。【参考方案3】:这是我的解决方案。它消除了创建额外注释的需要。
-
创建 2 个基本控制器。
@Controller
public class BaseController
@Controller
public class BaseRestController
-
使用选定的基本控制器扩展每个控制器。
@Controller
public class UserController extends BaseController
@RestController
public class UserRestController extends BaseRestController
-
创建 2 个全局控制器并在每个控制器中仅包含扩展基类。
@ControllerAdvice(assignableTypes = BaseController.class)
public class GlobalController
@ControllerAdvice(assignableTypes = BaseRestController.class)
public class GlobalRestController
【讨论】:
【参考方案4】:进一步构建@bruno-leite 的解决方案 1:
您可以定义一个元注释,因此每次插入 2 个时您只需要 1 个注释:
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Controller;
import java.lang.annotation.*;
/**
* Alias annotation for @Controller so that @ControllerAdvice instances can
* target the web controllers separately from the rest controllers.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
public @interface WebController
@AliasFor(
annotation = Controller.class
)
String value() default "";
在网络控制器上使用它:
@WebController
@RequestMapping("/customers")
public class CustomerController
最后定义一个@ControllerAdvice
,它只针对这样的网络控制器:
@ControllerAdvice(annotations = WebController.class)
public class GlobalControllerAdvice
@ModelAttribute("displayName")
public String getDisplayName(@AuthenticationPrincipal KeycloakAuthenticationToken principal)
if (principal != null)
String givenName = principal.getAccount().getKeycloakSecurityContext().getToken().getGivenName();
String familyName = principal.getAccount().getKeycloakSecurityContext().getToken().getFamilyName();
return givenName + " " + familyName;
else
return null;
(此示例确保我的 Thymeleaf 模板始终具有 displayName
属性,但也可以轻松调整异常处理程序)
【讨论】:
【参考方案5】:使用顺序修复错误:
@Order(1)
@ControllerAdvice(annotations = RestController.class)
public class ErrorRestControllerAdvice
@Order(2)
@ControllerAdvice(annotations = Controller.class)
public class ErrorControllerAdvice
【讨论】:
我已经发布了这个解决方案。这个答案并没有给这个话题带来任何新的东西以上是关于Spring:RestController 和 Controller 的不同异常处理程序的主要内容,如果未能解决你的问题,请参考以下文章
Spring中@Controller和@RestController之间的区别
spring BootSpring中@Controller和@RestController之间的区别
Spring:RestController 和 Controller 的不同异常处理程序
Spring 的@Controller 和@RestController的区别