【Spring MVC】REST APIにて任意の例外をハンドリングする方法

概要

REST APIアプリにて、任意の例外をハンドリングする方法についてまとめた。

あわせて読みたい

概要 REST APIアプリにて、エラーメッセージをカスタマイズする方法についてまとめた。 以下の記事の続きとなる。

【Spring MVC】REST APIにて基本的なエラーメッセージをカスタマイズする方法

 

前提

動作確認はTalend API Testerを使用した。
Talend API Testerの使用方法については以下を参照。

あわせて読みたい

概要 GUIのツールを使用してシンプルにREAT APIアプリにリクエストを送りたいと思い、Talend API Testerを使用してみた。 いろいろと便利だったため、基本的な使用方法について簡単にまとめた。   事[…]

Talend API Testerを使用してREST APIにリクエストする方法

 

任意の例外ハンドリング

リソース検索時、検索結果が0件の場合は例外をスローする。
アプリ側でスローされた例外をエラーハンドリングクラスで取得し、任意のエラーメッセージをクライアント側に返却する。
リソース検索については以下を参照。

あわせて読みたい

概要 Rest APIアプリを作成し、GET通信でリソース検索を行う方法についてまとめた。

REST APIにて基本的なリソース検索を行う方法

 

コントローラークラス

リソース検索結果が0件の場合、例外をスローする。

 

Rest07Controller.java


package com.example.rest_prototype.web.controller.rest07;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import com.example.rest_prototype.biz.service.ResourceService;
import com.example.rest_prototype.web.input.ResourceQuery;
import com.example.rest_prototype.web.resources.Resource;

@RestController
public class Rest07Controller {

	/** ビジネスロジック */
	@Autowired
	private ResourceService service;

	@GetMapping(value = "rest07/")
	public List<Resource> get(ResourceQuery queryParam) {
		// 検索結果
		List<Resource> resList = service.findByParam(queryParam);
		if (resList.size() == 0) {
			// リソースがない場合、例外をスローする
			throw new ResponseStatusException(HttpStatus.NOT_FOUND, "リソースがありませんでした");
		}

		return resList;
	}

}

 

throw new ResponseStatusException(HttpStatus.NOT_FOUND, “リソースがありませんでした”);

ResponseStatusExceptionにHTTP ステータスコード404を設定してスローしている。
この例外は自動でスローされることはなく、開発者が手動でスローできるクラス。
任意のHTTP ステータスコードを設定してスローできるため、柔軟なエラーハンドリングが可能となる。

 

エラーハンドリングクラス

入力チェックエラーハンドリングを行うために継承したResponseEntityExceptionHandlerは、ResponseStatusExceptionをハンドリングするメソッドを提供していないため、オーバーライドできるメソッドは存在しない。
今回も共通エラーハンドリングクラスとしてこのクラスを使用するため、新しくでResponseStatusExceptionをハンドリングするメソッドを追加する。

 

CustomExceptionHandler.java


package com.example.rest_prototype.web.advice;

import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
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.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.example.rest_prototype.web.resources.error.ErrorInfo;
import com.example.rest_prototype.web.resources.error.ValidateErrorInfo;

/**
 * 例外共通ハンドリングクラス
 *
 */
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

	/** ValidationMessages.propertiesからカスタムメッセージの取得 */
	@Autowired
	private MessageSource messageSource;

	/**
	 * 入力チェックエラー(リクエストボディなど)
	 */
	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		// エラー情報取得
		var errors = createInputError(ex.getBindingResult());

		return ResponseEntity.status(status).body(errors);
	}

	/**
	 * 入力チェックエラー(クエリパラメータなど)
	 */
	@Override
	protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,
			WebRequest request) {
		// エラー情報取得
		var errors = createInputError(ex.getBindingResult());

		return ResponseEntity.status(status).body(errors);
	}

	/**
	 * エラー情報作成
	 * @param br
	 * @return
	 */
	private ValidateErrorInfo createInputError(BindingResult br) {
		var errorInfo = new ValidateErrorInfo();
		errorInfo.setStatus(HttpStatus.BAD_REQUEST.value());
		errorInfo.setErrorTitle("入力エラー");
		errorInfo.setErrorMsg("入力に誤りあり");
		errorInfo.setErrorCode("SAMPLE_ERR0001");

		br.getFieldErrors()
		.stream()
		.forEach(e -> {
			// メッセージのキーを取得
			String messageKey = e.getCode() + "." + e.getField().getClass().getName();
			// フィールド名をメッセージキーに変換
			String fieldName = messageSource.getMessage(e.getField(), null, Locale.JAPANESE);
			// カスタムメッセージを取得
			String msg = messageSource.getMessage(messageKey, new Object[] { fieldName }, Locale.JAPANESE);
			errorInfo.addDetails(e.getField(), msg);
		});

		return errorInfo;
	}

	/**
	 * リソースなしエラー共通ハンドリング
	 * @param e
	 * @return
	 */
	@ExceptionHandler(ResponseStatusException.class)
	public ResponseEntity<ErrorInfo> handleResponseStatusException(ResponseStatusException e) {
		var errorInfo = new ErrorInfo();
		errorInfo.setStatus(e.getStatus().value());
		errorInfo.setErrorTitle("リソースエラー");
		errorInfo.setErrorMsg(e.getReason());
		errorInfo.setErrorCode("SAMPLE_ERR0002"); // エラーコード例

		return new ResponseEntity<>(errorInfo, HttpStatus.NOT_FOUND);
	}
}

 

@ControllerAdvice
public class CustomExceptionHandler

例外ハンドリングを行うクラスでは、@ControllerAdviceを付与する。

 

@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ErrorInfo> handleResponseStatusException(ResponseStatusException e) {

@ExceptionHandlerにハンドリングしたい例外クラスを設定することで、例外をキャッチできる。
尚、メソッドの引数に対象の例外クラスを指定することで、例外オブジェクト情報を取得する。

 

動作確認

リソースが0件になるように、リクエストを送信する。

 

リクエスト


http://localhost:8080/rest_prototype/rest07/?name=あ

 

レスポンス


{
    "status": 404,
    "errorTitle": "リソースエラー",
    "errorMsg": "リソースがありませんでした",
    "errorCode": "SAMPLE_ERR0002"
}

 

まとめ

 

☑ アプリ側にてスローされた例外は、@ControllerAdviceを付与したクラスでエラーハンドリングできる

☑ @ExceptionHandlerにキャッチしたい例外クラスを指定したメソッドを用意して、例外ハンドリングを行う

☑ ResponseStatusExceptionにHTTPステータスコードを設定することで、柔軟な例外ハンドリングが可能

 

スポンサーリンク