概要
REST APIアプリにて、任意の例外をハンドリングする方法についてまとめた。
概要 REST APIアプリにて、エラーメッセージをカスタマイズする方法についてまとめた。 以下の記事の続きとなる。
前提
動作確認はTalend API Testerを使用した。
Talend API Testerの使用方法については以下を参照。
概要 GUIのツールを使用してシンプルにREAT APIアプリにリクエストを送りたいと思い、Talend API Testerを使用してみた。 いろいろと便利だったため、基本的な使用方法について簡単にまとめた。 事[…]
任意の例外ハンドリング
リソース検索時、検索結果が0件の場合は例外をスローする。
アプリ側でスローされた例外をエラーハンドリングクラスで取得し、任意のエラーメッセージをクライアント側に返却する。
リソース検索については以下を参照。
コントローラークラス
リソース検索結果が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;
}
}
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);
}
}
public class CustomExceptionHandler
例外ハンドリングを行うクラスでは、@ControllerAdviceを付与する。
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ステータスコードを設定することで、柔軟な例外ハンドリングが可能