情報系学部生日記

備忘録や勉強したことのまとめ

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の引数は以下のようになっている。

  1. 引っ掛けた例外オブジェクト
  2. レスポンスボディ
  3. レスポンスヘッダ
  4. レスポンスステータス
  5. カレントリクエス

まとめ

今回はBadRequestExceptionだけ作ったが、必要に応じて他のレスポンスステータスを持つ例外クラスを作ってthrowするだけで色々な例外に対応できる。 今回はコントローラ共通の例外ハンドラを作ったが、コントローラ特有のハンドラを作ればそちらが優先されるらしいので、 汎用的な例外ハンドラはControllerAdviceで実装し、特殊なレスポンスを返すなどの条件があればコントローラに個別定義すれば良い。

参考リンク

qiita.com