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; } }