S-JIS[2017-09-02] 変更履歴

Spring Boot例外処理

Spring Bootの例外処理方法のメモ。


概要

ControllerRestControllerのメソッドで発生した例外や、そのメソッドに来るまでに発生した例外を捕捉することが出来る。

各Controllerクラス内に例外処理を行うメソッド(ハンドラー)を置くことが出来る。
また、全クラス共通の例外処理を行うメソッド(ハンドラー)を定義することも出来る。

これらのメソッドは例外クラス毎に定義する。
同じ例外クラスのハンドラーがControllerクラスと共通クラスにある場合は、Controllerのハンドラーが処理する。


Controllerの例外ハンドラーの例

RestControllerクラスの例外処理の例。(Spring Boot 1.5.6)

src/main/java/com/example/demo/rest/ApiController.java:

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かを判別して呼び分けてくれたりはしない。

src/main/java/com/example/demo/web/WebExceptionHandler.java:

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へ遷移
	}
}

src/main/java/com/example/demo/api/ApiExceptionHandler.java:

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

Spring Bootへ戻る / Spring Frameworkへ戻る / 技術メモへ戻る
メールの送信先:ひしだま