web-dev-qa-db-ja.com

SpringMVCの例外ハンドラー

プロジェクト内のすべてのコントローラーをインターセプトする例外ハンドラーを作成したいと思います。それは可能ですか?各コントローラーにハンドラーメソッドを配置する必要があるようです。ご協力いただきありがとうございます。 Json応答を送信するスプリングコントローラーがあります。したがって、例外が発生した場合は、1か所から制御できるエラー応答を送信したいと思います。

20
fastcodejava

(Spring 3.1で実装する方法を見つけました。これは、この回答の2番目の部分で説明されています)

Springリファレンスの 16.11例外の処理 の章を参照してください。

@ExceptionHandlerを使用する以外にもいくつかの方法があります( goukiの回答 を参照)

  • HandlerExceptionResolverポートレットパッケージではなくサーブレットを使用する)-ある種のグローバル@ExceptionHandlerを実装できます。
  • 例外の特定のロジックがなく、特定のビューしかない場合は、 SimpleMappingExceptionResolver を使用できます。これは、少なくともHandlerExceptionResolverの実装であり、例外を指定できます。名前パターンと、例外がスローされたときに表示されるビュー(jsp)。例えば:

    <bean
       class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
       p:defaultErrorView="uncaughtException">
       <property name="exceptionMappings">
           <props>
               <prop key=".DataAccessException">dataAccessFailure</prop>
               <prop key=".TypeMismatchException">resourceNotFound</prop>
               <prop key=".AccessDeniedException">accessDenied</prop>
            </props>
        </property>
     </bean>
    

Spring 3.2 +では、クラスに@ControllerAdviceで注釈を付けることができ、このクラスのすべての@ExceptionHandlerメソッドはグローバルに機能します。


Spring 3.1には、@ControllerAdviceはありません。しかし、少しハックすれば、同様の機能を持つことができます。

重要なのは、@ExceptionHandlerがどのように機能するかを理解することです。 Spring 3.1には、クラス ExceptionHandlerExceptionResolver があります。このクラスは(スーパークラスの助けを借りて)インターフェースを実装します HandlerExceptionResolver そして@ExceptionHandlerメソッドを呼び出す責任があります。

HandlerExceptionResolverインターフェースにはメソッドが1つだけあります。

ModelAndView resolveException(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler,
                              Exception ex);`.

リクエストがSpring3.x Controllerメソッドによって処理された場合、このメソッド(org.springframework.web.method.HandlerMethodで表される)はhandlerパラメーターです。

ExceptionHandlerExceptionResolverhandlerHandlerMethod)を使用してControllerクラスを取得し、@ExceptionHandlerで注釈が付けられたメソッドをスキャンします。このメソッドの1つが例外(ex)に一致する場合、このメソッドは例外を処理するために呼び出されます。 (それ以外の場合は、この例外リゾルバーが責任を感じないことを通知するためにnullが返されます)。

最初のアイデアは、HandlerExceptionResolverのように動作する独自のExceptionHandlerExceptionResolverを実装することですが、コントローラークラスで@ExceptionHandlerを検索する代わりに、1つの特別なBeanでそれらを検索する必要があります。欠点は、すべてのNiceメッセージコンバーター、引数リゾルバー、および戻り値ハンドラーを手動で構成する必要がある(コピー(またはサブクラスExceptionHandlerExceptionResolver)必要がある)ことです(実際の1つだけのExceptionHandlerExceptionResolverは春までに自動的に行われます)。だから私は別のアイデアを思いついた:

例外をTHE(構成済み)HandlerExceptionResolverに「転送」する、単純なExceptionHandlerExceptionResolverを実装しますが、グローバル例外ハンドラーを含むBeanを指す変更されたhandlerを使用します。 (すべてのコントローラーで機能するため、グローバルと呼びます。).

そしてこれが実装です:GlobalMethodHandlerExeptionResolver

import Java.util.List;
import Java.util.concurrent.ConcurrentHashMap;
import Java.util.concurrent.ConcurrentMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;


public class GlobalMethodHandlerExeptionResolver
             implements HandlerExceptionResolver, Ordered {

    @Override
    public int getOrder() {
        return -1; //
    }

    private ExceptionHandlerExceptionResolver realExceptionResolver;

    private List<GlobalMethodExceptionResolverContainer> containers;

    @Autowired
    public GlobalMethodHandlerExeptionResolver(
            ExceptionHandlerExceptionResolver realExceptionResolver,
            List<GlobalMethodExceptionResolverContainer> containers) {
        this.realExceptionResolver = realExceptionResolver;
        this.containers = containers;
    }

    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {              
        for (GlobalMethodExceptionResolverContainer container : this.containers) {    
            ModelAndView result = this.realExceptionResolver.resolveException(
                    request,
                    response,
                    handlerMethodPointingGlobalExceptionContainerBean(container),
                    ex);
            if (result != null)
                return result;
        }
        // we feel not responsible
        return null;
    }


    protected HandlerMethod handlerMethodPointingGlobalExceptionContainerBean(
                               GlobalMethodExceptionResolverContainer container) {
        try {
            return new HandlerMethod(container,
                                     GlobalMethodExceptionResolverContainer.class.
                                          getMethod("fakeHanderMethod"));            
        } catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException(e);
        }            
    }
}

グローバルハンドラーは、このインターフェイスを実装する必要があります(検出され、fakeHanderMethodに使用されるhandlerを実装するためです。

public interface GlobalMethodExceptionResolverContainer {
    void fakeHanderMethod();
}

そして、グローバルハンドラーの例:

@Component
public class JsonGlobalExceptionResolver
             implements GlobalMethodExceptionResolverContainer {

    @Override
    public void fakeHanderMethod() {
    }


    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ValidationErrorDto handleMethodArgumentNotValidException(
                MethodArgumentNotValidException validationException,
                Locale locale) {

         ...
         /* map validationException.getBindingResult().getFieldErrors()
          * to ValidationErrorDto (custom class) */
         return validationErrorDto;
    }
}

ところで:Springは例外リゾルバー用にGlobalMethodHandlerExeptionResolverを実装するすべてのBeanを自動的に登録するため、HandlerExceptionResolverを登録する必要はありません。したがって、単純な<bean class="GlobalMethodHandlerExeptionResolver"/>で十分です。

23
Ralph

Spring 3.2以降、 @ ControllerAdvice アノテーションを使用できます。 @ControllerAdviceクラス内で @ ExceptionHandler メソッドを宣言できます。この場合、allコントローラーからの@RequestMappingメソッドからの例外を処理します。 。

@ControllerAdvice
public class MyGlobalExceptionHandler {

    @ExceptionHandler(value=IOException.class)
    public @ResponseBody String iOExceptionHandler(Exception ex){
        //
        //
    }

    // other exception handler methods
    // ...

}
15

例外ハンドラーを定義する抽象クラスが行います。そして、コントローラーにそれを継承させます。

9
gouki