@ExceptionHandler不会捕获从Spring Formatters

时间:2016-05-19 11:43:30

标签: java spring spring-mvc exception-handling spring-boot

我有一个控制器,其中包含一个show方法,用于显示有关所提供实体的信息。

@Controller
@RequestMapping("/owners")
public class OwnersController {

  @RequestMapping(value = "/{owner}", method = RequestMethod.GET,
          produces = MediaType.TEXT_HTML_VALUE)
  public String show(@PathVariable Owner owner, Model model) {
    // Return view
    return "owners/show";
  }
}

要调用此操作,我使用http://localhost:8080/owners/1 URL。如您所见,我提供了所有者标识符1。

为了能够将标识符1转换为有效的Owner元素,需要定义Spring Formatter并在addFormatters WebMvcConfigurerAdapter方法上注册它。

我有以下OwnerFormatter

public class OwnerFormatter implements Formatter<Owner> {

  private final OwnerService ownerService;
  private final ConversionService conversionService;

  public OwnerFormatter(OwnerService ownerService,
      ConversionService conversionService) {
    this.ownerService = ownerService;
    this.conversionService = conversionService;
  }

  @Override
  public Owner parse(String text, Locale locale) throws ParseException {
    if (text == null || !StringUtils.hasText(text)) {
      return null;
    }
    Long id = conversionService.convert(text, Long.class);
    Owner owner = ownerService.findOne(id);
    if(owner == null){
        throw new EntityResultNotFoundException();
    }
    return owner;
  }

  @Override
  public String print(Owner owner, Locale locale) {
    return owner == null ? null : owner.getName();
  }
}

正如您所看到的,我使用findOne方法获取ID为1的Owner。但是,如果此方法返回null,因为没有任何ID为1的所有者,会发生什么?

为了防止这种情况,我抛出了一个名为EntityResultNotFoundException的自定义异常。此异常包含以下代码:

public class EntityResultNotFoundException extends RuntimeException {
    public EntityResultNotFoundException() {
        super("ERROR: Entity not found");
    }
}

我想配置项目,以便在此异常抛出时返回errors/404.html,因此,在Spring documentation之后,我有两个选项:

选项a)

使用管理@ControllerAdvice的{​​{1}}带注释的方法配置@ExceptionHandler

EntityResultNotFoundException

但是这不起作用。如果我改变上面的实现以在控制器方法中抛出异常,那就完美了。

似乎在调用控制器实现之前调用的Spring Formatter不会被@ControllerAdvice public class ExceptionHandlerAdvice { @ExceptionHandler(EntityResultNotFoundException.class) public String handleEntityResultNotFoundException(){ return "errors/404"; } } 监视。

对我来说这没有意义,因为在调用控制器方法之前调用了Spring Formatter来准备所提供的@ControllerAdvice的必要参数...所以Spring知道将调用哪个方法...为什么用于准备请求的Spring Formatter没有被@RequestMapping监视,以捕获在这个“准备过程”中发生的所有可能的异常?

更新:正如Serge Ballesta在答案中所说,@ ControllerAdvice是作为围绕控制器方法的AOP建议实现的。所以它无法拦截控制器外部抛出的异常。

我拒绝这个选项。

更新2:在Spring Framework JIRA中的一些答案之后,they suggest me使用捕获所有异常的通用@ControllerAdvice,然后使用一些条件来检查根本原因并且能够知道异常原因是否是我在Spring Formatter上调用的异常。我认为这可能是Spring MVC的另一个改进,因为我无法使用@ExceptionHandler来捕获Spring Formatters抛出的异常。您可以查看我上传的here

证明

我也拒绝这个选项。

选项b)

@ExceptionHandler类中添加新的@Bean SimpleMappingExceptionResolver,以使用视图标识符映射异常。

@Configuration

但是,上面的实现不适用于Spring Formatters抛出的异常。

UPDATE :我一直在调试Spring代码,我发现了两件可能对Spring Framework有所改进的事情。

首先,@Configuration public class WebMvcConfiguration extends WebMvcConfigurerAdapter { [...] @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("EntityResultNotFoundException", "errores/404"); resolver.setExceptionMappings(mappings); return resolver; } } 正在从DispatcherServletHandlerExceptionResolver方法加载所有已注册的initStrategies。此方法以正确的顺序采用所有HandlerExceptionResolvers,但是,然后,使用以下代码再次对它们进行排序:

initHandlerExceptionResolvers

问题是此方法委托AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); 方法尝试从作为findOrder实例的HandlerExceptionResolver获取顺序。正如您所看到的,我没有在已注册的@Bean上定义顺序,因此当尝试从我声明的bean Ordered获取顺序时使用LOWEST_PRECEDENCE。这导致Spring使用SimpleMappingExceptionResolver,因为它是第一个返回结果的。

所以,为了解决这个问题,我使用以下代码为我声明的bean添加了订单值。

DefaultHandlerExceptionResolver

现在,当 @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("EntityResultNotFoundException", "errores/404"); resolver.setOrder(-1); resolver.setExceptionMappings(mappings); return resolver; } 对所有已注册的AnnotationAwareOrderComparator进行排序时,HandlerExceptionResolver是第一个,它将用作解析器。

无论如何,还没有工作。我继续调试,我发现现在正在使用SimpleMappingExceptionResolver中的doResolveException来解决异常,所以没关系。但是,尝试获取映射视图的方法SimpleMappingExceptionResolver将返回null。

问题是findMatchingViewName正在尝试检查收到的异常是否与findMatchingViewName的exceptionMappings上定义的异常匹配,但它只检查SimpleMappingExceptionResolver方法中的超类。应该有必要检查原因异常。

我已应用以下解决方法继续工作(只需扩展getDepth并实现SimpleMappingExceptionResolver方法,尝试再次找到匹配的视图,如果深度无效则会导致异常)

findMatchingViewName

我认为这个实现非常有趣,因为也使用cause异常类而不是仅使用超类异常。我将在Spring Framework github上创建一个新的Pull-Request,包括这种改进。

通过这两个更改(顺序和扩展public class CauseAdviceSimpleMappingExceptionResolver extends SimpleMappingExceptionResolver{ /** * Find a matching view name in the given exception mappings. * @param exceptionMappings mappings between exception class names and error view names * @param ex the exception that got thrown during handler execution * @return the view name, or {@code null} if none found * @see #setExceptionMappings */ @Override protected String findMatchingViewName(Properties exceptionMappings, Exception ex) { String viewName = null; String dominantMapping = null; int deepest = Integer.MAX_VALUE; for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) { String exceptionMapping = (String) names.nextElement(); int depth = getDepth(exceptionMapping, ex); if (depth >= 0 && (depth < deepest || (depth == deepest && dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) { deepest = depth; dominantMapping = exceptionMapping; viewName = exceptionMappings.getProperty(exceptionMapping); }else if(ex.getCause() instanceof Exception){ return findMatchingViewName(exceptionMappings, (Exception) ex.getCause() ); } } if (viewName != null && logger.isDebugEnabled()) { logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() + "], based on exception mapping [" + dominantMapping + "]"); } return viewName; } } ),我能够捕获从Spring Formatter抛出的异常并返回自定义视图。

1 个答案:

答案 0 :(得分:2)

  

看起来像在调用控制器实现之前调用的Spring Formatter没有被@ControllerAdvice监视

好的捕获,这正是发生的事情。

  

为什么用于准备请求的Spring Formatter没有被@ControllerAdvice监视,以捕获在“准备过程”期间发生的所有可能的异常

因为@ControllerAdvice是作为围绕控制器方法的AOP建议实现的。所以它无法拦截控制器外部抛出的异常。

作为一种变通方法,您可以声明全局HandlerExceptionResolver来处理自定义异常。