繁体   English   中英

@ExceptionHandler不会捕获从Spring Formatters抛出的异常

[英]@ExceptionHandler doesn't catch exceptions being thrown from Spring Formatters

我有一个控制器,其中包含一个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转换为有效用户元件是必要的,以限定弹簧格式化,并将其注册在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文档之后 ,我有两个选择:

选项a)

使用管理EntityResultNotFoundException @ExceptionHandler注释方法配置@ControllerAdvice

@ControllerAdvice
public class ExceptionHandlerAdvice {
   @ExceptionHandler(EntityResultNotFoundException.class)
   public String handleEntityResultNotFoundException(){
      return "errors/404";
   }
}

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

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

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

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

我拒绝这个选择。

更新2:在Spring Framework JIRA中得到一些答案之后, 他们建议我使用一个捕获所有异常的通用@ExceptionHandler ,然后使用一些条件来检查根本原因并且能够知道异常原因是否是我的异常已经调用了Spring Formatter。 我认为这可能是Spring MVC的另一个改进,因为我无法使用@ExceptionHandler来捕获从Spring Formatters抛出的异常。 您可以查看我在此处上传的证明

我也拒绝这个选项。

选项b)

@Configuration类中包含一个新的@Bean SimpleMappingExceptionResolver以使用视图标识符映射异常。

@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;
  }
}

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

更新 :我一直在调试Spring代码,我发现两件事可能是Spring Framework的一个有趣的改进。

首先, DispatcherServletinitStrategiesinitHandlerExceptionResolvers方法加载所有已注册的HandlerExceptionResolver 此方法以正确的顺序获取所有HandlerExceptionResolvers,但随后,使用以下代码再次对它们进行排序:

AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);

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

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

  @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是第一个,它将用作解析器。

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

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

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

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;
    }

}

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

通过这两个更改(顺序和扩展SimpleMappingExceptionResolver ),我能够捕获从Spring Formatter抛出的异常并返回自定义视图。

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM