概要
REST APIアプリにて、エラーメッセージをカスタマイズする方法についてまとめた。
以下の記事の続きとなる。
概要 REST APIアプリにて、入力チェックを行った際のエラーハンドリング方法についてまとめた。 エラーメッセージはBean Validationのデフォルトメッセージを使用する。
前提
動作確認はTalend API Testerを使用した。
Talend API Testerの使用方法については以下を参照。
概要 GUIのツールを使用してシンプルにREAT APIアプリにリクエストを送りたいと思い、Talend API Testerを使用してみた。 いろいろと便利だったため、基本的な使用方法について簡単にまとめた。 事[…]
エラーメッセージをカスタマイズ
メッセージソースを使用して、外部ファイル(プロパティ)からメッセージ内容を取得する。
メッセージソースを使用するには、ResourceBundleMessageSourceまたはReloadableResourceBundleMessageSourceをBean登録する。
詳細は以下を参照。
概要 Springのメッセージ管理を行うMessageSourceを使用して外部ファイルからメッセージを取得する方法についてまとめた。 MessageSourceはプロパティファイルに定義したメッセージを取得する機能を提供する。 […]
メッセージソース
メッセージソースをDIするため、ルートアプリケーションコンテキストのBean定義ファイルにメッセージソース(ResourceBundleMessageSource)を定義する。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- コンポーネントスキャン -->
<context:component-scan
base-package="com.example.rest_prototype.biz" />
<!-- メッセージソース -->
<!-- ResourceBundleMessageSourceを使用する場合 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames" value="ValidationMessages" />
</bean>
</beans>
上記はメッセージソースが「ValidationMessages.properties」を参照することを意味する。
「ValidationMessages.properties」はクラスパス上(src/main/resources)に配置する。
メッセージプロパティ
メッセージプロパティにカスタマイズしたいメッセージを定義する。
プロパティの文字コードは「ISO-8859-1」とする。
ValidationMessages.properties
# デフォルトのValidationMessages.propertiesに定義
######################################
# プロパティ名
######################################
name=名前
id=リソースID
hogeDate=日付
######################################
# エラーメッセージ
######################################
# 汎用的なエラーメッセージ
NotNull.java.lang.String={0}を入力してください
エラーハンドリングクラス
入力チェックエラー時のメッセージをプロパティから取得する。
CustomExceptionHandler.java
package com.example.rest_prototype.web.advice;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
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.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
/**
* 例外共通ハンドリングクラス
*
*/
@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 Map<String, String> createInputError(BindingResult br) {
// フィールド名、デフォルトメッセージのマップ情報を返却
var errors = new HashMap<String, String>();
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);
errors.put(e.getField(), msg);
});
return errors;
}
}
private MessageSource messageSource;
メッセージソースのDIを行う。
「NotNull.java.lang.String」という文字列を取得している。
上記はメッセージプロパティに定義した、エラーメッセージのひな型を定義しているメッセージキーとなる。
「名前」という文字列を取得している。
上記はメッセージプロパティに定義した、「name」に紐づく値を取得している。
「名前を入力してください」という文字列を取得している。
上記は「NotNull.java.lang.String」とバインドする値(名前)を渡して、メッセージプロパティからメッセージ内容を取得している。
動作確認
サーバーを起動して、Talend API Testerなどを使用して動作確認を行う。
エラーメッセージ確認
nameフィールドをクエリパラメータに設定しない状態でリクエスト送信する。
メッセージプロパティから取得したエラーメッセージが表示された。
リクエスト
http://localhost:8080/rest_prototype/rest06/?hogeDate=2025-02-01
レスポンス
{"name":"名前を入力してください"}
エラーメッセージ実装例
ハンドリングしたエラーメッセージは様々カスタマイズが可能となる。
その他エラーメッセージの例を紹介する。
以下のようなエラー情報を返す例。
エラー情報
{
"status": 400,
"errorTitle": "入力エラー",
"errorMsg": "入力に誤りあり",
"errorCode": "SAMPLE_ERR0001",
"errors": [
{
"target": "name",
"msg": "名前を入力してください"
},
{
"target": "id",
"msg": "リソースIDを入力してください"
}
]
}
このエラー情報では、以下のような汎用的なエラー情報を返却している。
・エラータイトル概要
・エラーメッセージ概要
・エラーコード
・バリデーションフィールドエラー情報リスト
共通エラー情報クラス
共通するエラーフィールドを保持したクラス。
このフィールドをベースとして、サブクラスで必要に応じてエラー情報を追加する。
ErrorInfo.java
package com.example.rest_prototype.web.resources.error;
import lombok.Data;
/**
* エラー情報クラス
*/
@Data
public class ErrorInfo {
/** レスポンスステータス */
private int status;
/** エラータイトル概要 */
private String errorTitle;
/** エラーメッセージ概要 */
private String errorMsg;
/** エラーコード(特に用意していないので、あくまでサンプル) */
private String errorCode;
}
バリデーションエラー情報クラス
共通エラー情報クラスを拡張した、バリデーションエラー情報クラス。
バリデーションエラー情報リストを追加で保持する。
ValidateErrorInfo.java
package com.example.rest_prototype.web.resources.error;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 入力チェックエラー情報クラス
*/
@Data
public class ValidateErrorInfo extends ErrorInfo {
/** 入力チェック項目別エラーリスト */
private List<Detail> errors = new ArrayList<>();
/**
* エラー詳細設定
* @param target
* @param msg
*/
public void addDetails(String target, String msg) {
errors.add(new Detail(target, msg));
}
/**
* バリデーションエラー詳細
* 内部クラス
*/
@Data
@AllArgsConstructor
private static class Detail {
/** 入力項目 */
private String target;
/** エラーメッセージ */
private String msg;
}
}
ErrorInfo(共通エラー情報クラス)を継承して拡張している。
private List<Detail> errors = new ArrayList<>();
バリデーションエラー詳細(Detailクラス)のリストを保持している。
バリデーションエラー詳細となる内部クラス。
エラーとなったフィールドとエラーメッセージ情報を保持する。
上記を呼び出すことで、バリデーションエラー詳細リストを追加していく。
エラーハンドリングクラス
作成したエラー情報をレスポンスとして返却する。
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.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
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;
}
}
まとめ
☑ メッセージソースはBean登録を行って使用する
☑ メッセージ内容をメッセージプロパティに定義することで、任意のメッセージを取得することができる
☑ メッセージソースを利用することで、柔軟にエラーメッセージ内容をカスタマイズできる