ControllerAdviceとResponseEntityExceptionHandlerを用いた例外ハンドラ
やること
Springで例外ハンドラを用意する方法は複数あるが、今回はResponseEntityExceptionHandlerを用いてアプリケーション内で共通の例外ハンドラを定義する。
ResponseEntityExceptionHandler (Spring Framework 5.0.0.RELEASE API)
環境
- Spring Boot 1.5.7.RELEASE
方法
投げる例外の基底クラスを作る
Transactionalでロールバックさせるため、RuntimeExceptionを継承した例外クラスを作る。
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.HttpStatus; abstract public class HttpStatusException extends RuntimeException { private String message; private HttpStatus httpStatus; HttpStatusException(HttpStatus httpStatus, String message) { this.httpStatus = httpStatus; this.message = message; } public HttpStatus getHttpStatus() { return this.httpStatus; } public String toResponseJson() throws JsonProcessingException { return new ObjectMapper().addMixIn(HttpStatusException.class, HttpStatusExceptionMixin.class).writeValueAsString(this); } @JsonAutoDetect( getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE ) abstract class HttpStatusExceptionMixin { @JsonProperty String message; } }
例外エラーのフィールドには返すメッセージとHTTPステータスを用意し、返却するJSONを生成するメソッドを実装する。 JSONで返す値はmessageだけで良いので、Mixin用の抽象クラスをインナークラスとして定義しておく。
派生例外クラスを作る
とりあえずBadRequest用のクラスを作る。
import org.springframework.http.HttpStatus; public class BadRequestException extends HttpStatusException{ public BadRequestException(String message) { super(HttpStatus.BAD_REQUEST, message); } }
ControllerAdviceを用いた例外ハンドラを作る
import com.fasterxml.jackson.core.JsonProcessingException; import com.example.exceptions.HttpStatusException; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @ControllerAdvice public class ApiAdviceController extends ResponseEntityExceptionHandler { @ExceptionHandler(HttpStatusException.class) public ResponseEntity handleHttpStatusException(HttpStatusException exception, WebRequest request) throws JsonProcessingException { return handleExceptionInternal(exception, exception.toResponseJson(), new HttpHeaders(), exception.getHttpStatus(), request); } }
ResponseEntityExceptionHandlerを継承したクラスを作り、ControllerAdviceアノテーションを付ける。 ExceptionHandlerをメソッドに付け、HttpStatusExceptionを引っ掛けてやるようにする。 handleExceptionInternalの引数は以下のようになっている。
- 引っ掛けた例外オブジェクト
- レスポンスボディ
- レスポンスヘッダ
- レスポンスステータス
- カレントリクエスト
まとめ
今回はBadRequestExceptionだけ作ったが、必要に応じて他のレスポンスステータスを持つ例外クラスを作ってthrowするだけで色々な例外に対応できる。 今回はコントローラ共通の例外ハンドラを作ったが、コントローラ特有のハンドラを作ればそちらが優先されるらしいので、 汎用的な例外ハンドラはControllerAdviceで実装し、特殊なレスポンスを返すなどの条件があればコントローラに個別定義すれば良い。