概要
Spring Boot でエラー時のレスポンスをカスタマイズしてみたので備忘録としてここに記載します。
前回の記事 では、独自例外を定義し、その例外をハンドルするようにしましたが、今回は Spring が独自に定義している例外のハンドルについて記載したいと思います。
経緯
前回の記事で定義した独自の例外を補足できたのですが、しかし、 MethodArgumentNotValidException
例外初め Spring が定義している例外が発生した際、そのエラー内容がボディ部が null で返ってきました。今回は、その原因と対応方針をまとめてみます。
前提条件
環境
- SpringBoot 2.1.3
- Java 1.8.0_181
原因
「Spring 徹底入門 Spring Framework による Java アプリケーション開発」には下記のように記載があります。
以下、引用をさせて頂きます。
ResponseEntityExceptionHandler には、Spring MVC のフレームワーク処理で発生する例外をハンドリングする @ExceptionHandler メソッドが実装されています。つまり、上記のようなクラスを作成するだけで、フレームワーク処理で発生する例外を全てハンドリングすることができます。ResponseEntityExceptionHandler クラスを継承したクラスを作成しただけだと、レスポンスボディはからの状態でエラー応答されます。
ResponseEntityExceptionHandler を継承したクラスを作成しただけだと、レスポンスボディは空の状態で応答されます。レスポンスボディにエラー情報を出力する場合は、 handleExceptionInternal メソッドをオーバーライドします。
直接的な原因はこれで分かりました。
前回の記事 では独自例外に対するハンドル処理は実装していたのですが、 Spring が定義している例外については一切ハンドル処理を定義していなかったため、例外が発生したとしてもボディ部がブランク表示されてしまったのだと思います。
( = ResponseEntityExceptionHandler
を継承したハンドルクラスを定義した段階で、独自例外だけではなく、他の例外が発生した時もそのハンドル方法を定義しないといけないらしいです。この辺り、あまり詳しくないのですので、詳しい方教えてください。。。)
今度は、それをコードベースで確認をしてみました。
親クラスである ResponseEntityExceptionHadler
クラスの MethodArgumentNotValidException
をハンドルする handleMethodArgumentNotValid
メソッドを確認すると、ボディ部に明示的に null
が渡されているし、デバックをするとこの処理を通っています。
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
handleExceptionInternal
の引数の内容や順番に関しては、 前回の記事 を参照ください。
対応方針
上記から分かるようにメソッドの引数のボディ部に明示的に null
を渡しているので、このメソッドをオーバーライドして、ボディ部に実際に表示させたいエラー内容を渡してあげれば表示されるはずです。
では、実際に動かしてみたいと思います。
コントローラの定義
package com.example.demo.springExceptionErrorResponse;
import com.example.demo.errorResponse.ErrorDetail;
import com.example.demo.errorResponse.MyException;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/springexceptionhandle")
public class HelloSpringExceptionController {
@RequestMapping("/test")
public void get(@RequestBody @Validated Person person, Model model) {
}
}
モデルクラスの定義
package com.example.demo.springExceptionErrorResponse;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class Person {
@NotNull
String firstName;
@NotNull
String lastName;
}
例外ハンドラクラスの定義
今回は簡単に "エラーだよ。"
という文字列を渡してみました。
ここに関してはエラー時のボディ用のクラス定義を行なうなどして、よしなに定義してみてください。
package com.example.demo.springExceptionErrorResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@RestControllerAdvice
public class SpringExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex,
Object object,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
return super.handleExceptionInternal(ex, "エラーだよ。", headers, status, request);
}
}
実行
下記、 JSON を http://localhost:8080/springexceptionhandle/test
に対して POST
でリクエストしてみます。
Person.java
には firstName
に対して @NotNull
制約が適応されているので、このリクエストはエラーになるはずです。
{
"firstName": null,
"lastName": "名前"
}
結果
エラーだよ。
上記はレスポンスボディとしては寂しいですが、エラー発生時時刻、ステータスコードなどを表示させたい場合には、前回の記事 のようにレスポンスボディ専用のクラスを定義して super.handleExceptionInternal
メソッドの2つ目の引数にそのクラスのインスタンスを渡してあげれば表示されるはずです。
関連記事
Spring Boot で REST API のエラー時のレスポンスをカスタマイズする(前編)
参考記事
今回、記事を記載する上で下記の記事を参考にさせて頂きました。
Spring 徹底入門 Spring Framework による Java アプリケーション開発
Spring Bootで作成したREST APIのエラーレスポンスをカスタムする
SpringBootでエラーハンドリング
SpringBootの@RestControllerで例外処理をする