在春天,您可以设置"全球"异常处理程序通过@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
而不是RestControllerExceptionHandler
被春天正确调用时(仅适用于使用@RestController
注释的类)....但是当我添加ControllerExceptionHandler
时通过ControllerExceptionHandler
。为什么?
答案 0 :(得分:8)
发生了这种情况,因为@RestController注释本身也是一个@Controller,所以Spring正在考虑@ annotation = Controller.class
的@ControllerAdvice。
你可以尝试另一个method来定义@ControllerAdvice将有效的控制器子集,所以这里有一些解决方案:
创建一个新的注释@NotRestController:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotRestController {
}
使用@Controller和@NotRestController标记不是@RestController的控制器:
@Controller
@NotRestController
@RequestMapping("/controller")
public class SampleController {
}
在NotRestController.class
上使用ControllerExceptionHandler
:
@ControllerAdvice(annotations = NotRestController.class)
public class ControllerExceptionHandler {
我已经使用Solution 1
创建了一个示例项目并在Github上共享:
https://github.com/brunocleite/springexceptionhandlertest
将@RestController类移动到包foo.bar
中,将@Controller类移到另一个包foo.nuk
中。
使用basePackages
的{{1}}属性代替@ControllerAdvice
:
annotations
祝你好运!
答案 1 :(得分:8)
经过深入调查后,字母顺序似乎很重要:/。
当我将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);
}
}
答案 2 :(得分:1)
要进一步构建@ 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
,仅将Web控制器定位为:
@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
属性,但也可以轻松地针对异常处理程序进行调整)
答案 3 :(得分:0)
这是我的解决方案。无需创建其他注释。
@Controller
public class BaseController
@Controller
public class BaseRestController
@Controller
public class UserController extends BaseController
@RestController
public class UserRestController extends BaseRestController
@ControllerAdvice(assignableTypes = BaseController.class)
public class GlobalController
@ControllerAdvice(assignableTypes = BaseRestController.class)
public class GlobalRestController
答案 4 :(得分:-3)
使用命令修复错误:
@Order(1)
@ControllerAdvice(annotations = RestController.class)
public class ErrorRestControllerAdvice {
}
@Order(2)
@ControllerAdvice(annotations = Controller.class)
public class ErrorControllerAdvice {
}