こんな方におすすめ
- SpringBootで開発している方
- SpringBootで例外処理を実装したい方
目標:SpringBootを使って自作の例外ケースを作ってエラー処理する
うまく実装できたときの様子が下の写真です。
存在しないhttp://localhost:8080/messages/222333にアクセスしたときに
「そんなの存在しないぞ〜!」と警告を出してくれています。
基本的にはどのエラー(405,400,etc...)でも同じ実装の流れでできるので、まずは今回モデルとして紹介していく例外処理の流れを抑えてみましょう!
今回の目標は存在しないページにアクセスしたときに404エラー(NotFound)を起こすように例外処理を実装することです。
今回例外処理を行いたいSpringBootのプログラムの概要
今回はCRUD(新規登録、取得、更新、削除)ができるプログラムにおいて、「存在しない値を取得しようとすると起きる」例外処理を実装します。
実際の処理を行うクラス(ロジッククラス)はサービスクラスなので、そこに通常処理に加えて例外時の処理を加えていきます。
ポイント
正常処理を行うクラスに例外処理を書くのは基本です
(プログラミングでよくあるAでなければB)
例外が起きたあとの流れ
サービスクラスで例外が投げられたあとの処理を図を使って説明します。
まず前提としてサービスクラスが「エラー」を投げて来ます。
そのエラーを例外コントローラクラスから指示を受けた例外ケース決定クラスが受けて、エラーメッセージで情報を格納します。
例外コントローラは監督
例外決定クラスは1つの例外しか受け取れないキャッチャー
エラーメッセージ格納クラスはエラー専門の伝達係だ!
通常だとストレートしか投げないピッチャーが、変化球(エラー)を投げて来たときに監督(例外コントローラクラス)が変化球専門のキャッチャー(例外決定クラス)に受け取る指示を出して、その変化球の感想をキャッチャーから聞いた伝達係(エラーメッセージ格納クラス)が伝えに帰って来てくれるのです
まとめ
例外コントローラは監督
例外決定クラスは1つの例外専門のキャッチャー
エラーメッセージ格納クラスは伝達係
図)例外Cが起きた時の流れ
SpringBootで実際に例外処理を実装しよう
全体の流れは把握できたでしょうか?
この章からは実際のソースコードを考えながら実装していきます。
SpringBoot例外処理〜サービスクラス〜
MessageService.java
/**
* サービスクラス
*/
@Service
public class MessageService {
/**
* リポジトリクラス呼び出し
*/
@Autowired
private MessageRepository repository;
/**
* idごとの取得
*
* @throws NotFoundException
*/
public Optional<Message> getMessageById(String id) throws NotFoundException {
Optional<Message> message = repository.findById(id);
if (!message.isPresent()) {
String errorMessage = "対象のデータが存在しません。 [message id:" + id + "]";
throw new NotFoundException(errorMessage);
}
return repository.findById(id);
}
}
サービスクラスは例外を発見して、投げるクラスになります。
Optional<Message> message = repository.findById(id);
if (!message.isPresent()) {
String errorMessage = "対象のデータが存在しません。 [message id:" + id + "]";
throw new NotFoundException(errorMessage);
}
この部分は
idに基づいてメッセージデータを探す
→もしメッセージがないならNotFoundExceptionエラーを"対象のデータが存在しません。 [message id:" + id + "]"というメッセージとともに表記する
を表しています。
SpringBoot例外処理〜例外コントローラクラス〜
GlobalExceptionHandler.java
/**
* 例外ハンドラークラス
*
* @author achan
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
/**
* 404エラー対策
*
* @param ex NotFound
* @param request ウェブリクエスト
* @return 404エラー
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler({ NotFoundException.class })
public ResponseEntity<Object> handle404(NotFoundException ex, WebRequest request) {
ErrorMessage res = new ErrorMessage();
res.setErrorMessage(ex.getMessage());
return super.handleExceptionInternal(ex, res, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
private ResponseEntity<Object> handleExceptionInternal(NotFoundException body, HttpStatus status) {
// TODO 自動生成されたメソッド・スタブ
return null;
}
}
このクラスはアプリケーション内で例外が起きたときに、例外のケースを受けるクラスを指定してあげる役割を果たします。
今回だと404エラー(見つかりませんでした)はNotFoundExceptionクラスが受けてくださいと指示を出しています。
@RestControllerAdvice
RestAPI向けに@ControllerAdviceと@ResponseBodyの役割を合成してくれたアノテーションです。
@ExceptionHandler内で@ResponseBodyの記述が要らなくなります。
@ControllerAdviceは記述することで@ExceptionHandlerに全てのコントローラークラスで発生する例外を対象をキャッチするという役割を持してくれます。
つまり「アプリケーション内のエラーを見張る権限を与える」アノテーションです。
@ExceptionHandler
例外をコントロールするクラスであることをSpringBootアプリケーションに教えてくれるクラスです。
今回は404エラーのみを指定していますが対象の例外は複数指定することができます。
@ResponseStatus
コントローラクラスがレスポンスを返す際のHTTPのステータスコードを指定しています。
今回だと404エラーです。
SpringBoot例外処理〜例外ケース決定クラス〜
NotFoundException.java
/**
* 404エラーを指定するクラス
*
* @author achan
*/
public class NotFoundException extends Exception {
private static final long serialVersionUID = 1L;
/**
* 404エラーメッセージを指定する
*
* @param errorMessage エラーのメッセージ内容を格納する
*/
public NotFoundException(String errorMessage) {
super(errorMessage);
}
}
このクラスでは例外ケースを404NotFoundに決定する役割を持っています。
同時にこの例外ケースはerrorMessageを持っています。
ということも宣言してくれています。
SpringBoot例外処理〜エラーメッセージ格納クラス〜
ErrorMessage.java
/**
* エラー用レスポンスクラス
*
* @author achan
*
*/
@Data
public class ErrorMessage {
/**
* * エラーメッセージ
*/
private String errorMessage;
}
このクラスはエラーが起きた時の文言をerrorMessageに格納してくれるクラスです。
サービスクラスを思い出しましょう。
String errorMessage = "対象のデータが存在しません。 [message id:" + id + "]";
という部分がありましたね。
ここの内容をGETしてSETしてくれるのがerrorMessageです。
もっと分かりやすく書きます
ポイント
サービスクラスがエラーメッセージを投げる
→例外コントローラクラスが例外決定クラスを呼び起こす
→例外決定クラスで例外パターンが決まる
→メッセージ格納クラスでエラーメッセージを取得して、格納(今回だと存在しないidが確定)
→例外決定クラスの中にエラーメッセージが入る
最初に例外が投げられて、例外の種類が決定しても該当するidは宙ぶらりんになっています。
そこでこのerrorMessageという箱に入れてあげて例外の種類分けに返してあげるのです。
ErrorMessage.javaの存在の認識のしかた
初めて例外処理をSpringBootで実装する際にエラーレスポンスのモデルクラス、今回でいう
「ErrorMessage.java」の存在に戸惑う方もいると思います。
そのようなかたが違和感を抱く理由はこのクラスはデータベースに参照するクラスではないからです。
今までこのようにGetterSetterが出てくるクラスはデータベースを参照するためにあると理解してきた方は多いはずです。
むしろその感覚は正常ですし、今までしっかり学習をしてきたからこそだと思います。
しかしこのエラーレスポンスのクラスはデータベースを参照しているわけではありません。
そもそもGetterSetterクラスはデータベースと参照するためだけのものではありません。
GetterSetterがあるクラス=対象をGETしてSETするために存在するクラス
このような認識を持っておきましょう。
対象がデータベースにある時はデータベースを参照する形になりますし、今回のように例外として弾かれたAPIを対象とする場合も同様にGETしてSETします。
ポイント
GetterSetterがあるクラスは外部の対象をGET、SETするクラス
色んな例外を実装してみよう
実はSpringBootでは予め例外のケースを用意してくれています。
Eclipsseで開発している方は例外コントローラクラスのResponseEntityExceptionHandlerにカーソルを合わしてF3を押してみてください。
ほら!色んな例外ケースが準備されています。
あとはここからメソッドを持ってきて例外コントローラクラスに記述&@Overrideしてしまえば簡単に実装できてしまいます。
(ウィンドウ→ビューの表示→アウトラインを設定すると見やすくなります)
試しにオーバーライドを使って例外処理を実装してみました★
今回は405エラー対策です。
SpringBootアプリケーションを実行して405エラーが起きる動作をしてみます。
お見事!!
例外処理は実際に実装して覚えてみよう
今回はSpringBootでの例外処理の実装を紹介しました。
理解することも大事ですが手を動かすことも大事です。
自分で手を動かしながら覚えていきましょう!