簡體   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