2016-05-19 18 views
3

私は、提供されたエンティティに関する情報を表示するshowメソッドを含むコントローラを持っています。私はhttp://localhost:8080/owners/1 URLを使用し、この操作を呼び出すために@ExceptionHandlerがSpringフォーマッタからスローされた例外をキャッチしない

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

識別子1を有効な所有者要素に変換するには、Spring Formatterを定義し、WebMvcConfigurerAdapteraddFormattersメソッドに登録する必要があります。

私は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(); 
    } 
} 

あなたが見ることができると、そこにいない任意のものであるので、私は、このメソッドはnullを返した場合happends何、ID 1と所有者を取得するためにfindOneメソッドを使用しましたが、 ID 1のオーナーですか?

これを防ぐために、EntityResultNotFoundExceptionというカスタム例外をスローします。私はこの例外をスローしたときerrors/404.htmlを返すことができるようにプロジェクトを設定したい

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

ので、Spring documentation次、私は2つのオプションがあります:この例外は、次のコードが含まれています

オプションA)

EntityResultNotFoundExceptionを管理

設定@ControllerAdvice@ExceptionHandlerと注釈を付け方法

しかし、これは動作しません。上記の実装をコントローラメソッド内で例外をスローするように変更すると、完璧に動作します。

呼び出しコントローラの実装が@ControllerAdviceでモニタされない前に呼び出されるSpring Formatterのようです。

私にとっては、提供された@RequestMappingの必要なパラメータを準備するためにコントローラメソッドを呼び出す前にSpring Formatterが呼び出されているので、これは意味がありません... Springはどのメソッドが呼び出されるかを知っています...なぜSpring要求を準備するために使用されたフォーマッタは、@ControllerAdviceによって監視されておらず、この「準備プロセス」中に発生した可能性のあるすべての例外をキャッチしていますか?

UPDATE:Serge Ballesta氏が答えてくれたように、@ControllerAdviceはコントローラのメソッドについてのAOPアドバイスとして実装されています。したがって、コントローラの外部にスローされた例外を傍受する方法はありません。

私はこのオプションを拒否します。春FrameworkのJIRAにおけるいくつかの回答の後、they suggest meは、すべての例外をキャッチし、一般的な@ExceptionHandlerを使用して、根本原因を確認するために、例外の原因があるかどうかを知ることができるように、いくつかの条件文を使用してする:UPDATE 2

私がSpring Formatterで呼び出した例外です。私は@ExceptionHandlerを使用してSpring Formattersからスローされた例外をキャッチすることができないので、これがSpring MVCの別の改善であると思います。アップロードした証明書を確認できます。here

このオプションも拒否しています。

オプションB)

ビュー識別子を持つ例外をマップするために@Configurationクラス内に新しい@BeanSimpleMappingExceptionResolverを含めます。

@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 Formatterでスローされた例外では機能しません。

更新日::私はSpringコードをデバッグしていましたが、Spring Frameworkの興味深い改善点が2つあります。

まず、DispatcherServletはすべてHandlerExceptionResolverinitStrategiesおよびinitHandlerExceptionResolversのメソッドからロードしています。この方法は、正しい順序ですべてのHandlerExceptionResolversを取られたが、その後、再びそれらを注文するには、次のコードを使用しています:

AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); 

問題があることHandlerExceptionResolverから順番を取得しようとしfindOrder方法でこのメソッドを委譲していますOrderedのインスタンスです。ご覧のように、私は登録された@Beanで注文を定義していないので、宣言されたbeanから注文を取得しようとすると、SimpleMappingExceptionResolverはLOWEST_PRECEDENCEを使用しています。これにより、最初に結果を返すので、SpringはDefaultHandlerExceptionResolverを使用します。

これを解決するために、次のコードを使用して宣言したBeanにorder値を追加しました。 AnnotationAwareOrderComparatorソートがすべてHandlerExceptionResolverSimpleMappingExceptionResolver登録今

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

は、最初の一つであり、それはリゾルバとして使用されます。

とにかく、まだ動作していません。私はデバッグを続行しましたが、現在はのdoResolveExceptionを使用して例外を解決していることがわかりました。ただし、マップされたビューを取得しようとするメソッドfindMatchingViewNameはnullを返します。

問題はfindMatchingViewNameSimpleMappingExceptionResolverのexceptionMappingsに定義されたいくつかの例外を除いて受け取った例外一致かどうかを確認しようとしているが、それは唯一のgetDepthメソッド内のスーパークラスをチェックするということです。原因の例外をチェックする必要があります。

私はこの実装だと思う私は

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

} 

(ちょうどSimpleMappingExceptionResolverを拡張し、深さが有効でない場合、原因の例外を除いて再びビューのマッチング見つけようとするfindMatchingViewNameメソッドを実装します)作業を継続するには、以下の回避策を適用した

スーパークラスの例外だけを使用するのではなく、原因の例外クラスを使用するので、本当に面白いです。この改善を含め、Spring Frameworkのgithubに新しいPull-Requestを作成します。

これらの2つの変更(注文と拡張SimpleMappingExceptionResolver)を使用すると、Spring Formatterからスローされた例外をキャッチしてカスタムビューを返すことができます。

+0

私は、Spring Frameworkで次の問題を作成しました。JIRA https://jira.spring.io/browse/SPR-14291 – jcgarcia

+0

これで問題が解決するかどうかわかりませんが、コントローラのアドバイスでクラスに注釈を付ける必要があると思います実行する4つの@Component、Repository、Service、Controllerのいずれかを使用します。したがって、ownerformatterクラスに注釈を付けることができるかどうかを確認してください。 –

答えて

1

はそれが起こるまさにだ、コントローラの実装が@ControllerAdvice

大漁でmonitorized beeingていません呼び出す前に呼び出された春のフォーマッタのように思えます。

春フォーマッタは、要求を調製するために使用される理由は、すべての可能な例外をキャッチする@ControllerAdviceによってmonitorized beeingていません@ControllerAdviceはAOPとして実装されているので、この「準備処理」

中に発生しましたコントローラのメソッドに関するアドバイス。したがって、コントローラの外部にスローされた例外を傍受する方法はありません。

回避策として、カスタム例外を処理するためにグローバルHandlerExceptionResolverを宣言することができます。

+0

こんにちはSerge、最初にあなたの答えに感謝します。私の質問に書いたように、私はSimpleMappingExceptionResolverでチェックしましたが、結果はありません。その後、私はSpringコードをデバッグしていましたが、Springバグや可能な改善があります。私はSpringFormatterから例外をキャッチするためにSimpleMappingExceptionResolverを使うという解決策で質問を更新します。 – jcgarcia

+0

私はプルリクエストを作成できるように、次のバグを作成しました。https://jira.spring.io/browse/SPR-14291 – jcgarcia

+0

'@ ExceptionHandler'は根本原因をチェックしていないようです。 '@ ControllerAdvice'とAOPについては何もありません。私の証明[ここ](https://github.com/DISID/disid-proofs/tree/master/spring-boot-exception-handling/ControllerAdvice)を参照してください。 – jcgarcia

関連する問題