Spring Bootの例外処理方法のメモ。
Controller・RestControllerのメソッドで発生した例外や、そのメソッドに来るまでに発生した例外を捕捉することが出来る。
各Controllerクラス内に例外処理を行うメソッド(ハンドラー)を置くことが出来る。
また、全クラス共通の例外処理を行うメソッド(ハンドラー)を定義することも出来る。
これらのメソッドは例外クラス毎に定義する。
同じ例外クラスのハンドラーがControllerクラスと共通クラスにある場合は、Controllerのハンドラーが処理する。
RestControllerクラスの例外処理の例。(Spring Boot 1.5.6)
package com.example.demo.rest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController;
@RestController
public class ApiController {
private static final Logger LOG = LoggerFactory.getLogger(ApiController.class);
〜
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleException(MethodArgumentNotValidException exception) {
LOG.warn("handleException", exception);
Map<String, String> body = Collections.singletonMap("message", "invalid");
return body;
}
}
例外を処理するメソッドには@ExceptionHandlerアノテーションを付け、処理する例外クラスを指定する。
メソッドの引数で発生した例外を受け取ることが出来る。
この例はRestControllerなので、メソッドの戻り値はレスポンスボディーとなる。
@ResponseStatusアノテーションでHTTPステータスコードを指定することが出来る。
参考: なみひらブログさんのSpringFrameworkでのJSON Validationの実装/動作確認メモ
メソッドの戻り値をResponseEntityにすると、レスポンスヘッダーを定義したりHTTPステータスコードを動的に変更したりすることが出来る。
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler;
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleException2(MethodArgumentNotValidException exception) {
LOG.warn("handleException", exception);
Map<String, String> body = Collections.singletonMap("message", "invalid");
HttpHeaders headers = new HttpHeaders();
headers.add("example", "zzz");
HttpStatus status = HttpStatus.BAD_REQUEST;
return new ResponseEntity<>(body, headers, status);
}
全クラス(複数のController)にまたがった横断的な例外処理をするには、専用のハンドラークラスを設ける。(Spring Boot 1.5.6)
ウェブ用の例外ハンドラークラスには@ControllerAdviceアノテーションを付ける。
Rest API用の例外ハンドラークラスには@RestControllerAdviceアノテーションを付ける。
これらのアノテーションの違いは、ハンドラーメソッドの戻り値の扱い。
@ControllerAdviceアノテーションの場合は(Controllerのメソッドと同様に)、次の処理のパスを返す。
@RestControllerAdviceアノテーションの場合は(RestControllerのメソッドと同様に)、レスポンスボディーを返す。
@ControllerAdviceアノテーションを付けたクラスと@RestControllerAdviceアノテーションを付けたクラスの2種類を用意した場合、どちらが優先されるかは不定。(クラス名(FQCN)のアルファベット順で若い方が呼ばれるっぽい雰囲気?たぶんディレクトリーの走査方法依存)
例外の発生元がControllerかRestControllerかを判別して呼び分けてくれたりはしない。
package com.example.demo.web; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class WebExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(WebExceptionHandler.class);
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleException(Exception exception) {
LOG.error("WebExceptionHandler", exception);
return "error1"; // error1.htmlへ遷移
}
}
package com.example.demo.rest; import java.util.Collections; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ApiExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(ApiExceptionHandler.class);
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, String> handleException(Exception exception) {
LOG.error("ApiExceptionHandler", exception);
Map<String, String> body = Collections.singletonMap("message", "ApiExceptionHandler");
return body;
}
}