2

假设我们有这样的@RestControllerAdvice-annotated 类:

@RestControllerAdvice
public class RestResponseExceptionHandler {

  @ExceptionHandler(MyBusinessException .class)
  public ResponseEntity<ErrorResponse> handleMyBusinessException (MyBusinessException ex) {
      return createResponseEntity(ex, ex.getErrorCode());
  }

  @ExceptionHandler({IllegalArgumentException.class, ValidationException.class, DataIntegrityViolationException.class})
  public ResponseEntity<ErrorResponse> handleInvalidPropertyException(RuntimeException ex) {
      return createResponseEntity(ex, ErrorCode.DATA_INVALID);
  }

  [...]

  @ExceptionHandler(RuntimeException.class)
  public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) {
        return createResponseEntity(ex, ErrorCode.UNKNOWN);
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleException(Exception ex) {
      return createResponseEntity(ex, ErrorCode.UNKNOWN);
  }

  @ExceptionHandler(WrapperException .class)
  public ResponseEntity<ErrorResponse> handleWrapperException (WrapperException ex) {
      Exception exception = ex.getWrappedException();
      // re-dispatch exception here
  }
}

对于已知的WrapperException,是否有可能以某种方式重新调度包装的异常?

我尝试了几件事,例如重新抛出包装的 excption 或显式调用我们的 ErrorController 的自定义方法并在那里重新抛出异常,但到目前为止没有运气。

4

3 回答 3

1

为什么要重新抛出可能会产生不必要的分支。您可以进行条件检查并调用适当的异常处理程序方法,如下所示。

@ExceptionHandler(WrapperException .class)
public ResponseEntity<ErrorResponse> handleWrapperException (WrapperException ex) {
  Exception exception = ex.getWrappedException();
  if (exception instanceof MyBusinessException) {
      return handleMyBusinessException((MyBusinessException) exception);
  }
  return //Default
 } 
于 2022-01-24T11:07:27.423 回答
1

我想我想出了春天的方式来做到这一点。您可以在RestResponseExceptionHandler这种类型的 bean 中自动装配 - HandlerExceptionResolver. Spring 为你自动配置了其中的一些,我设法使它与这个一起工作 - handlerExceptionResolver。像这样的东西:

@RestControllerAdvice
public class ErrorHandler {

    private static final String BEAN1 = "handlerExceptionResolver";
    private static final String BEAN2 = "customResolver";

    private final HandlerExceptionResolver resolver;

    @Autowired
    public ErrorHandler(@Qualifier(BEAN2) HandlerExceptionResolver resolver) {
        this.resolver = resolver;
    }

    @ExceptionHandler(WrapperException.class)
    public void exception(WrapperException exception, HttpServletRequest request, HttpServletResponse response) {
        Exception wrappedException = exception.getWrappedException();
        //this will dispatch the handling to the handler for the wrapped exception
        this.resolver.resolveException(request, response, null, wrappedException);
    }

    @ExceptionHandler(IndexOutOfBoundsException.class)
    public ResponseEntity<Object> indexOutOfBoundsException() {
        return ResponseEntity.of(Optional.of("IndexOutOfBoundsException"));
    }

    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<Object> runtimeException() {
        return ResponseEntity.of(Optional.of("NullPointerException"));
    }

    @ExceptionHandler(IOException.class)
    public ResponseEntity<Object> ioException() {
        return ResponseEntity.of(Optional.of("IOException"));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Object> illegalArgumentException() {
        return ResponseEntity.of(Optional.of("IllegalArgumentException"));
    }
}

编辑:更改为 autowire 自定义 bean,spring 自动配置 bean 的名称仍然存在。

您必须在包装的异常处理程序中使用请求和响应,否则将无法处理它。当您resolveException()使用包装的异常调用时,它会将其重新路由到包装异常的正确处理程序。我使用控制器进行了一些测试,以抛出像这样的异常,一切都得到了正确解决。

@Controller
public class ExcController {

    @GetMapping("/thr")
    public String throwExc() throws Exception {
        throw new Exception(new NullPointerException());
    }

    @GetMapping("/thr2")
    public String throwExc2() throws Exception {
        throw new IOException();
    }
}

编辑:找到另一个实现HandlerExceptionResolver,它有效 - ExceptionHandlerExceptionResolver。为了避免已经存在的 bean 的循环依赖,声明自定义解析器来处理 WrapperExceptions:

@Configuration
public class ExceptionResolverConfig {

    @Bean
    public HandlerExceptionResolver customResolver() {
        return new ExceptionHandlerExceptionResolver();
    }
}

然后在 ErrorHandler 中自动装配这个 bean。ExceptionHandlerExceptionResolver在没有任何额外配置的情况下为我工作。

于 2022-01-24T12:57:03.760 回答
0

从 Spring 5.3 开始,已经在尝试查找匹配项时@ExceptionHandler查看了异常。cause因此,如果您使用的是最新的 Spring 版本,您可以删除您的@ExceptionHandler(WrapperException.class)方法,它应该可以按预期工作。

Spring是否匹配顶级(根)异常或包装异常的决定有点涉及,并且在手册中进行了充分解释:https ://docs.spring.io/spring-framework/docs/5.3.15/参考/html/web.html#mvc-ann-exceptionhandler

获得正确顺序工作的最简单方法是在它们自己的类中编写最不特定的异常处理程序(即 forException和类似的),并为此 bean 定义低优先级,例如 with 。然后应该在一个或多个更高优先级的 bean 中定义特定包装异常的处理程序。RuntimeException@ControllerAdvice@Order(Ordered.LOWEST_PRECEDENCE)

由于这似乎具有相当高的回归潜力(例如,通过重构和改组),因此对异常处理的正确优先级进行某种测试或测试非常重要。

于 2022-01-24T13:28:56.950 回答