Spring Boot 基本

Spring BootのFilter内でエラーが発生した場合の例外処理を追加してみた

WEBアプリケーションを作成する際、処理の途中でエラーが発生した場合に、特定のエラーページに遷移してエラーメッセージを表示したり、エラーログを出力したりする必要がある。

Spring BootのWEBアプリケーションにおいては、処理の途中で例外が発生した場合は、何もしなくても「resources/templates/error.html」に画面遷移するようになっている。また、RequestDispatcherFilterやErrorControllerによって、エラー時の画面遷移先を変えたり、エラーメッセージの文言を変えたり、エラーログの出力機能を追加したりすることもできる。

今回は、Filter内でエラーが発生した場合のサンプルプログラムを通して、Spring Bootの例外処理について共有する。Spring Bootデフォルトのエラー処理・RequestDispatcherを利用したエラー処理・ErrorControllerを利用したエラー処理の3種類について共有する。

前提条件

以下の記事のSpring BootのWEB画面へのフィルタ追加が完了していること。

Spring BootでFilterを利用してみた前回、このブログで「AOP(Aspect Oriented Programming)」について述べていたが、AOPと同じように複数のプロ...

やってみたこと

  1. Spring Bootデフォルトのエラー処理
  2. RequestDispatcherによるエラー処理
  3. ErrorControllerによるエラー処理

 

Spring Bootデフォルトのエラー処理

今回は、フィルタ処理内(DemoFilter2.java)でエラーを発生させ、エラーログを出力後、エラー画面(error.html)に遷移するサンプルプログラムを作成した。

今回作成したプログラム構成は以下の通り。
Spring Bootデフォルトのエラー処理プログラムの構成

また、今回変更したプログラムの内容は以下の通り。DemoFilter2.javaでは、エラーログを出力後に発生した例外をそのままスローしているだけとなっているが、これだけで「error.html」に遷移するようになっている。

package com.example.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import java.io.IOException;

public class DemoFilter2 implements Filter {
    //ログ出力のためのクラス
    private static Log log = LogFactory.getLog(DemoFilter2.class);

    /**
     * 処理(本プログラムではコントローラクラスのメソッド)の前後にログ出力
     * を行うフィルタ定義
     * @param request サーブレットリクエスト
     * @param response サーブレットレスポンス
     * @param chain フィルタチェイン
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response
            , FilterChain chain) throws IOException, ServletException {
        log.debug("DemoFilter2 started.");
        try{
            //ここでArithmeticExceptionを強制的に発生させる
            int i = 1 / 0;
        }catch(Exception e){
            //エラーログを出力
            log.error(e.toString());
            //例外をスローすると、resources/templates/error.htmlに遷移
            throw e;
        }
        //一連の処理(本プログラムではコントローラクラスのメソッド)を実行
        chain.doFilter(request, response);
        log.debug("DemoFilter2 ended.");
    }
}
<!DOCTYPE html>
<html lang="ja">
    <meta charset="UTF-8">
    <title>error page</title>
</head>
<body>
    Error Occured!!
    <p>システムエラーが発生しました、ログを確認してください。</p>
</body>
</html>

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-filter2/default/demo

その後、Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスした結果は以下の通りで、エラー画面(error.html)に遷移する。
Spring Bootデフォルトのエラー処理プログラムの実行結果

また、その際のコンソール・ログ出力結果は以下の通りで、下図赤枠部分で、「DemoFilter2.java」の開始ログ出力後に、エラーログが出力されているのが確認できる。

●コンソール
Spring Bootデフォルト時のコンソール

●ログファイル(C:/work/logs/demo.log)
Spring Bootデフォルト時のログ



RequestDispatcherによるエラー処理

今回は、RequestDispatcherによってエラー画面に遷移するコントローラのメソッドを、パラメータを付与した形で呼び出すサンプルプログラムを作成した。

今回作成したプログラム構成は以下の通り。
RequestDispatcherによるのエラー処理プログラムの構成

また、今回変更したプログラムの内容は以下の通り。DemoFilter2.javaでは、エラーログを出力後に、RequestDispatcherによって、「/error_filter」のパス(DemoController.javaに記載)に遷移するようになっている。また、その際にexceptionというパラメータを渡すようにしていて、画面上に発生したエラー内容が表示できるようになっている。

package com.example.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.RequestDispatcher;
import java.io.IOException;

public class DemoFilter2 implements Filter {
    //ログ出力のためのクラス
    private static Log log = LogFactory.getLog(DemoFilter2.class);

    /**
     * 処理(本プログラムではコントローラクラスのメソッド)の前後にログ出力を行うフィルタ定義
     * @param request サーブレットリクエスト
     * @param response サーブレットレスポンス
     * @param chain フィルタチェイン
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response
           , FilterChain chain) throws IOException, ServletException {
        log.debug("DemoFilter2 started.");
        try{
            //ここでArithmeticExceptionを強制的に発生させる
            int i = 1 / 0;
        }catch(Exception e){
            //エラーログを出力
            log.error(e.toString());
            StackTraceElement[] errors = e.getStackTrace();
            for(StackTraceElement element : errors){
                log.error(element);
            }
            //RequestMappingが「/error_filter」であるコントローラのメソッド
            //(DemoController.java、errorFilter)を呼び出す
            RequestDispatcher rd = request.getRequestDispatcher(
                           "/error_filter?exception=" + e.toString());
            rd.forward(request, response);
            return;
        }
        //一連の処理(本プログラムではコントローラクラスのメソッド)を実行
        chain.doFilter(request, response);
        log.debug("DemoFilter2 ended.");
    }
}
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class DemoController {

    /**
     * 初期表示を行う
     * @return 初期表示画面のパス
     */
    @RequestMapping("/")
    public String index(){
        return "index";
    }

    /**
     * フィルタ内でエラーが発生した場合の画面遷移を行う
     * @param exception パラメータとして渡された例外
     * @param mav ModelAndViewオブジェクト
     * @return ModelAndViewオブジェクト
     */
    @RequestMapping("/error_filter")
    public ModelAndView errorFilter(@RequestParam("exception") String exception
             , ModelAndView mav){
        //resources/templates下のerror.htmlに遷移
        mav.setViewName("error");
        //error.htmlに埋め込むパラメータ「errClass」に、
        //パラメータとして渡された例外を埋め込む
        mav.addObject("errClass", exception);
        return mav;
    }
}
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
    <meta charset="UTF-8">
    <title>error page</title>
</head>
<body>
    Error Occured!!
    <p>エラーが発生しました、ログを確認してください。</p>
    <p th:text="'発生したエラー:  ' + ${errClass}">
        ここに発生したエラーのクラス名が設定されます
    </p>
</body>
</html>

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-filter2/requestdispatcher/demo

その後、Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスした結果は以下の通りで、エラー画面(error.html)に遷移し、パラメータで渡したエラー内容が表示されることが確認できる。
RequestDispatcherによるエラー処理プログラムの実行結果

また、その際のコンソール・ログ出力結果は以下の通りで、下図赤枠部分で、「DemoFilter2.java」の開始ログ出力後に、エラーログが出力されているのが確認できる。

●コンソール
RequestDispatcherによるエラー処理プログラムのコンソール

●ログファイル(C:/work/logs/demo.log)
RequestDispatcherによるエラー処理プログラムのログ



ErrorControllerによるエラー処理

今回は、フィルタ処理内(DemoFilter2.java)でエラーを発生させた後で、ErrorControllerクラスをimplementsしたクラス内(DemoExceptionController.java)で、エラーログの出力とエラー画面(error_filter.html)への遷移を行うサンプルプログラムを作成した。

今回作成したプログラム構成は以下の通り。
ErrorControllerのエラー処理プログラムの構成

また、今回変更したプログラムの内容は以下の通り。DemoFilter2.javaでは、エラーログを出力後にRuntimeException例外をスローしていて、これがruntimeExceptionHandlerメソッド(DemoExceptionController.javaに記載)で処理できるようになっている。

package com.example.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import java.io.IOException;

public class DemoFilter2 implements Filter {
    //ログ出力のためのクラス
    private static Log log = LogFactory.getLog(DemoFilter2.class);

    /**
     * 処理(本プログラムではコントローラクラスのメソッド)の前後にログ出力を行うフィルタ定義
     * @param request サーブレットリクエスト
     * @param response サーブレットレスポンス
     * @param chain フィルタチェイン
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response
          , FilterChain chain) throws IOException, ServletException {
        log.debug("DemoFilter2 started.");
        try{
            //ここでArithmeticExceptionを強制的に発生させる
            int i = 1 / 0;
        }catch(Exception e){
            throw new RuntimeException(e);
        }
        //一連の処理(本プログラムではコントローラクラスのメソッド)を実行
        chain.doFilter(request, response);
        log.debug("DemoFilter2 ended.");
    }
}
package com.example.demo;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
 * RuntimeException発生時の処理を実装
 * 通常、SpringBootアプリケーションでエラーが発生した場合は resources/templates/error.html に
 * 遷移するようになっているが、その遷移先を変えたり、文言追加オブジェクト等を追加するには、
 * このクラスのように、ErrorControllerクラスをimplementsしたクラスを作成する
 */
@Controller
public class DemoExceptionController implements ErrorController {

    /**
     * エラーが発生した場合の画面遷移
     * @param e RuntimeException例外
     * @param mav ModelAndViewオブジェクト
     * @return ModelAndViewオブジェクト
     */
    @RequestMapping("/error")
    @ExceptionHandler(RuntimeException.class)
    public ModelAndView runtimeExceptionHandler(RuntimeException e, ModelAndView mav){
        mav.setViewName("error_filter");
        mav.addObject("errMsg", "システムエラーが発生しました、ログを確認してください。");
        return mav;
    }

    /**
     * エラーパスを取得
     * (このメソッドを追加しないとコンパイルエラーになるため追加)
     * @return エラーパス
     */
    @Override
    public String getErrorPath() {
        return "";
    }
}
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
    <meta charset="UTF-8">
    <title>error page</title>
</head>
<body>
    Error Occured!!
    <p th:text="${errMsg}">ここに発生したエラーメッセージが設定されます</p>
</body>
</html>

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-filter2/errorcontroller/demo

その後、Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスした結果は以下の通りで、エラー画面(error_filter.html)に遷移する。
フィルターのエラー処理プログラム3の実行結果

また、その際のコンソール・ログ出力結果は以下の通りで、下図赤枠部分で、「DemoFilter2.java」の開始ログ出力後に、エラーログが出力されているのが確認できる。

●コンソール
ErrorControllerのエラー処理プログラムのコンソール

●ログファイル(C:/work/logs/demo.log)
ErrorControllerのエラー処理プログラム3のエラーログ

要点まとめ

  • Spring BootのWEBアプリケーションにおいては、処理の途中で例外が発生した場合は、何もしなくても「resources/templates/error.html」に画面遷移するようになっている。
  • RequestDispatcherクラスのオブジェクトでforwardすることによって、例外処理を行うメソッドに遷移することができる。
  • ErrorControllerをimplementsしたクラスを利用した例外処理も行える。