【问题标题】:Spring MVC: determine which controller threw exception in @ExceptionHandlerSpring MVC:确定哪个控制器在@ExceptionHandler中抛出异常
【发布时间】:2017-06-17 11:15:31
【问题描述】:

我将 Spring MVC 与 Spring Boot 和 Thymeleaf 一起使用。我有返回 Thymeleaf 模板名称的普通控制器和带有 @RepsonseBody 注释的 REST 控制器。

假设我有一个EntityNotFoundException,它由控制器调用的某些代码抛出。如果被抛出,我想分别返回 404 状态码和 REST 控制器的错误页面或错误消息。

我目前对普通控制器的设置:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
      super(message);
    }
}

@Controller
public class FooController {

  @RequestMapping("/foo") public String foo() {
    try {
       ...
    } catch (EntityNotFoundException enfe) {
      throw new ResourceNotFoundException(enfe.getMessage());
    }
    return "foo";
  }
}

对于 REST 控制器,我不会捕获异常,而是让全局异常处理程序来处理它:

@Controller
public class BarController {

  @RequestMapping("/bar")
  @ResponseBody
  public SomeDto bar() throws EntityNotFoundException {
    ...
    return someDto;
  }
}

@ControllerAdvice
public class ExceptionHandlerAdvice {

  @ExceptionHandler(EntityNotFoundException.class)
  public final ResponseEntity<Object> handleEntityNotFoundExceptionEntityNotFoundException enfe) {
    return new ResponseEntity<>(enfe.getMessage, HttpStatus.NOT_FOUND);
  }
}

我也不想在我的普通控制器中捕捉并重新抛出。全局处理程序应该同时处理这两个:

  @ExceptionHandler(EntityNotFoundException.class)
  public final Object handleEntityNotFoundException(EntityNotFoundException enfe) {
    if (/* is REST controller? */) {
      return new ResponseEntity<>(enfe.getMessage(), HttpStatus.NOT_FOUND);
    } else {
      Map<String, Object> model = ImmutableMap.of("message", enfe.getMessage());
      return new ModelAndView("error", model, HttpStatus.NOT_FOUND);
    }
  }

有没有办法确定异常的来源,即控制器是否用@ResponseBody或类似的东西注释?

【问题讨论】:

    标签: java spring spring-mvc


    【解决方案1】:

    我找到了解决方案。您可以将当前控制器方法作为HandlerMethod 参数注入。

      @ExceptionHandler(EntityNotFoundException.class)
      public final Object handleEntityNotFoundException(EntityNotFoundException enfe, HandlerMethod handlerMethod) {
    
        boolean isRestController = handlerMethod.hasMethodAnnotation(ResponseBody.class)
            || handlerMethod.getBeanType().isAnnotationPresent(ResponseBody.class)
            || handlerMethod.getBeanType().isAnnotationPresent(RestController.class);
    
        if (isRestController) {
          return new ResponseEntity<>(enfe.getMessage(), HttpStatus.NOT_FOUND);
        } else {
          Map<String, Object> model = ImmutableMap.of("message", enfe.getMessage());
          return new ModelAndView("error", model, HttpStatus.NOT_FOUND);
        }
      }
    

    【讨论】:

    • 当我这样做时,我会得到一个Failed to invoke @ExceptionHandler method: public java.lang.Object blabla.lExceptionHandler.handleException(java.lang.Exception,org.springframework.web.method.HandlerMethod) java.lang.IllegalStateException: No suitable resolver for argument [1] [type=org.springframework.web.method.HandlerMethod] 我可能需要告诉 Spring 在其他地方传递handlerMethod 吗?
    【解决方案2】:

    我会为普通控制器和 REST 控制器使用单独的异常,而不是使用特殊的返回码或消息做一些魔术。这样您就可以编写简单的目标异常处理程序。

    @ResponseStatus(HttpStatus.NOT_FOUND)
    public class ResourceNotFoundException extends RuntimeException {
        public ResourceNotFoundException(String message) {
          super(message);
        }
    }
    
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public class RestResourceNotFoundException extends RuntimeException {
        public ResourceNotFoundException(String message) {
          super(message);
        }
    }
    

    【讨论】:

    • 这与我想要实现的完全相反。这样,我的 REST 控制器也需要重新抛出异常。我当前的解决方案并非如此,处理程序建议已经涵盖了 REST。我也不想在普通控制器中抛出ResourceNotFoundExceptions。
    【解决方案3】:

    您可以使用错误代码作为源原因/点或为从 ResourceNotFoundException 扩展的不同异常源创建单独的异常。

    对于错误代码,您使用一个异常并使用不同的代码,对于另外的异常,您应该创建 exrta 类。

    public class ResourceNotFoundException extends RuntimeException {
        private Long sourceErrorCode;//if you use java8 use Optional
    
        public ResourceNotFoundException(String message) {
          super(message);
        }
    
        public ResourceNotFoundException(String message , Long sourceErrorCode) {
          super(message);
          this.sourceErrorCode = sourceErrorCode;
        }    
    }
    

    投掷

     ......
    catch (EntityNotFoundException enfe) {
      throw new ResourceNotFoundException(enfe.getMessage() , ERROR_SOURCE_CODE);
    }
    

    和处理程序

     @ExceptionHandler(EntityNotFoundException.class)
      private Set<Long> REST_CODES = ..add... {code1 , code2...}
    
      public final Object handleSlugNotFoundException(EntityNotFoundException enfe) {
        //if you use java 8 use Optional
        if (REST_CODES.contains(enfe.getErrorCode())/* is REST controller? */) {
          return new ResponseEntity<>(enfe.getMessage(), HttpStatus.NOT_FOUND);
        } else {
          Map<String, Object> model = ImmutableMap.of("message", enfe.getMessage());
          return new ModelAndView("error", model, HttpStatus.NOT_FOUND);
        }
      }
    

    并为错误代码创建类持有者或枚举以避免代码重复

    【讨论】:

    • 我想摆脱ResourceNotFoundException。关键是直接处理EntityNotFoundException,而不是将其作为前端特定的异常重新抛出。
    • 您可以使用方面来重新抛出和包装异常。在 ControllerAdvice 中,您不知道异常来自何处。这就像全局每个类型的异常 try catch... 但你有 stacktrace ,你可以从 stacktrace 找到异常的来源,但更好地重新抛出方面。
    • 您尽量避免重新抛出,因为它是一个额外的异常或在源代码中?对于源代码使用方面,在使用 stacktra e 时获得额外的异常
    猜你喜欢
    • 1970-01-01
    • 2020-06-02
    • 2016-03-10
    • 2018-11-11
    • 1970-01-01
    • 2016-09-16
    • 1970-01-01
    • 2014-05-29
    • 2015-04-25
    相关资源
    最近更新 更多