【今更ながらSpring BootでWEB開発 #8】例外処理

Web

今回の目標

前回はREST APIの実装方法について簡単に説明しました。またREST APIの確認用のツールの説明も少ししました。

【今更ながらSpring BootでWEB開発 #7】 REST APIの作成
今回はSpring BootでREST APIを実装する方法について説明します。REST APIについてもアノテーションを駆使することで簡単に実装することができます。またREST APIを確認するためのツールについても少し触れます。

今回は例外処理について軽く触れたいと思います。

エラーページの作成

例えば実装していない適当なURLを表示しようとします。するとTomcatの「404 Not Fount」のエラーが表示されます。実際にこのようなページが表示されるのはあまり望ましくないでしょう。

Spring Bootではtemplates直下に「error.html」を作成することで共通のエラーページを作成することができます。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
      <h1>システムに問題が発生しました</h1>
    <h2 th:text="${status} + ' ' + ${error}">500 Internal Server Error</h2>
    <a th:href="@{/}">トップに戻る</a>
  </body>
</html>

エラーページでは「status」と「error」を取得できます。存在しないURLを表示しようとした場合は「status: 404」、「error: Not Found」とHttpステータスの内容が格納されています。

404以外にController以降の処理で例外が発生した場合は「500 Internal Server Error」が発生します。

Httpステータスごとにエラーページを分けたい場合は、templates直下に「error/404.html」のようにファイルを作成します。

個別に設定

例えば前々回作成したお問い合わせ情報の詳細ページ(/inquiry/detail/{id})は、パスパラメータによって該当するお問い合わせ情報を取得していました。ではパスパラメータのIDにデータベースには存在しないIDを指定するとどうなるでしょうか。

実際にやってみると例外が発生し、先ほどのエラーページを実装していたら「500 Internal Server Error」が表示されます。Spring Data JDBCのqueryForMapでは、対象のレコードが存在しない場合に「EntityResultDataAccessException」が発生します。

今回は対象のデータがない場合はお問い合わせ一覧のページ(/inquiry/list)にリダイレクトするように実装してみます。

@GetMapping("/detail/{id}")
public String detail(@PathVariable("id") int id, Model model) {
  try {
    model.addAttribute("inquiry", service.findById(id));
  } catch (EmptyResultDataAccessException e) {
    return "redirect:/inquiry/list";
  }
  return "inquiry/detail.html";
}

実装自体はシンプルで、通常のJava同様にtry-catchで例外を処理します。これについてはJavaをやっていれば説明は不要だと思います。

コントローラーごとに設定

例えば「InquiryController」で実装している処理で発生する例外を共通的に処理したい場合、次のように「@ExceptionHandler」を使用します。

@Controller
@RequestMapping("/inquiry")
public class InquiryController {

  //これまでの実装

  @ExceptionHandler({ Exception.class })
  public String handleException(Exception e, HttpServletResponse response, Model model) {
    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    model.addAttribute("message", e.getMessage());
    return "inquiry/error.html";
  }
}

上記の場合、「InquiryController」で発生するすべての例外はここで処理されるようになります。ただし、個別で対応しているtry-catchの分は当然対象外です。

引数にExceptionクラスを設定することで、例外の内容を取得することができます。

特定の例外のみ処理したい場合は、アノテーションで指定しているExceptionクラスを指定のクラスに変更します。例えば、Java Data JDBCのDataAccessExceptionを処理したい場合は、

@ExceptionHandler({ DataAccessException.class })

とします。自作したExceptionも指定できます。

全体の設定

すべてのコントローラーに共通する例外処理を設定することができます。そのためにはControllerAdviceクラスを作成します。

ControllerAdviceクラスはすべてのコントローラーで共通する処理を定義することができます。詳しくは説明しませんが、例外処理はその共通する処理として定義できる1つです。

@ControllerAdvice
public class CustomControllerAdvice {

  @ExceptionHandler
  public String handleException(Exception e) {
    return "error/index.html";
  }
  
}

ControllerAdviceクラスはクラスのアノテーションとして「@ControllerAdvice」を追加します。REST APIの場合は「@RestControllerAdvice」を追加します。

例外処理の定義自体は先ほどコントローラーで設定した場合と同じです。特定の例外を処理したい場合はアノテーションにExceptionクラスを指定します。

例外処理の優先度

上記で紹介した例外の優先度は、個別設定(try-catch)、コントローラー設定、全体設定の順になります。「404 Not Found」などはコントローラーで処理する前に処理されます。

あとがき

@ExceptionHandlerや@ControllerAdviceを使用することで簡単に例外を共通的に処理することができます。普通に実装するよりtry-catchの数が減りますし、なんとなく直感的でわかりやすいと思います。

 

- Spring Bootのおすすめ書籍はコチラ -

コメント

タイトルとURLをコピーしました