SpringBoot/例外処理
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
検索
|
最終更新
|
ヘルプ
]
開始行:
[[SpringBoot]]
* Spring Boot における例外処理 [#qbf253f0]
Spring Bootにおける例外処理のプラクティスを色々調べたので...
** 基本的な考え方 [#ne8afdc9]
+ Javaプログラムにおいて何らかのエラーを検出する際には,...
-- 返り値でエラーを表現してはいけない.
+ 例外は必ず ''非検査例外 (extends RuntimeException)''と...
-- 検査例外(extends Exception)をすると,throws宣言やtry-c...
-- 呼び出し先の検査例外をcatchする必要があれば,非検査例...
try {
//検査例外を投げる呼び出し
} catch(検査例外 e) {
throw new 非検査例外("メッセージ", e); //非検査例外...
}
+ 例外のハンドリングは,専用の ''例外ハンドラ'' で横断的...
+ 例外が発生した場合,Webアプリ・Web-APIの呼び出し元であ...
-- サーバ側で復旧・解決できるエラーについてはこの限りでは...
+ エラーを伝える際には,適切なHTTPステータスをつけられれ...
-- デフォルトではすべて500(Internal Server Error)になって...
** アーキテクチャ [#ebd73770]
&attachref(exception-handling.png);
** 拾うべき例外とその層 [#s76ad8ed]
拾うべきエラーはそれぞれの層で種類が異なる.
Springに関する記事,マニュアルをいろいろと調べた中で,
大体下記のような感じか.
|Springの層|拾うべき例外の種類|例|h
|コントローラ層|サービス利用の前提に違反した例外|フォーム...
|サービス層|ビジネスルールに違反した例外|ユーザIDは他の人...
|レポジトリ層|インフラやシステムの不具合による例外|入出力...
** カスタム例外 [#ubf799e2]
Spring Frameworkが発生させる例外は一般的なものなので,
''アプリケーション固有の例外 (カスタム例外)''でラップして...
|Spring例外|例外発生場所|ラップするカスタム例外|h
|NoSuchElementException|@ServiceでRepository内の口座を参...
|MethodArgumentNotValidException|@RestController でフォー...
例外の種類だけでなく,起こった場所や文脈に応じて,例外に'...
|例外タイプ|検査エラーの内容|エラーコード|h
|不正な操作|フォーム検査にひっかかった|PirCoinValidationE...
|不正な操作|新規ユーザIDが既に存在した|PirCoinValidationE...
|不正な操作|残高以上のお金を出勤しようとした|PirCoinValid...
例外サンプル
/**
* アカウントに対する不正な操作に関する例外
*/
public class AccountValidationException extends RuntimeE...
/** 定義済みエラーコード */
public static final int ACCOUNT_ALREADY_EXISTS = 11;
public static final int INVALID_UID = 12;
public static final int INVALID_AMOUNT = 13;
public static final int INSUFFICIENT_BALANCE = 14;
private static final long serialVersionUID = 1L;
/** エラーコード */
public final int code;
public AccountValidationException(int code, String s...
super(str, cause);
this.code = code;
}
public AccountValidationException(int code, String s...
super(str);
this.code = code;
}
}
使用例
/**
* 与えられたユーザIDの口座からコインを出金する.
*
* @param uid 口座のユーザID
* @param amount 出金する枚数
* @return 出金後の口座情報
*/
public Account useCoin(String uid, int amount) {
if (amount <= 0) {
throw new AccountValidationException(AccountValidation...
"amount must be a positive number");
} else {
int coin = getCoin(uid);
if (coin - amount < 0) { // 残高が0未満にならないかチ...
throw new AccountValidationException(AccountValidatio...
"Cannot afford " + amount + " coins");
} else {
return setCoin(uid, coin - amount);
}
}
}
** 例外のタイプと対応するHTTPステータスコード [#f01f9b3b]
|一般的なJava例外|カスタム例外|対応するHTTPコード|h
|ElementNotFound|そんなリソースないよ例外|404 Not found|
|IllegalArgumentException|そんな引数おかしいよ例外|400 Ba...
|IllegalStateException|アクセスのタイミングがおかしいよ例...
|AuthenticationException|認証失敗したよ例外|401 Unauthori...
|AccessDeniedException,SecurityException|アクセスしては...
|UnsupportedOperationException|そんな操作ないよ例外|501 N...
|Throwable (anything else)|その他の例外|500 Internal Serv...
** 例外ハンドラ [#p77d4ca6]
- @RestControllerに対応する例外ハンドラは,@RestControlle...
- アーキテクチャ的には,例外ハンドラは ''コントローラ層''...
- キャッチする例外の型とその時に返すHTTPステータスコード...
サンプルソース
@RestControllerAdvice
public class PitCoinErrorHandler {
/**
* アカウントが存在しない
* @param req
* @param ex
* @return
*/
@ExceptionHandler(AccountNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public PitCoinResponse handleNotFoundException(HttpSe...
log.warn(ex.getMessage());
return PitCoinResponse.createErrorResponse(ex.cod...
}
/**
* アカウントに対する操作が不正
*/
@ExceptionHandler(AccountValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public PitCoinResponse handleIlleagalArgumentExceptio...
log.warn(ex.getMessage());
return PitCoinResponse.createErrorResponse(ex.cod...
}
/**
* 許可されないメソッドの実行
* @param request
* @param ex
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedExcept...
@ResponseStatus(HttpStatus.FORBIDDEN)
public PitCoinResponse handleMethodNotSupportedExcept...
return PitCoinResponse.createErrorResponse(ex);
}
/**
* 上記で処理していないエラー
*
* @param request
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public PitCoinResponse handleException(HttpServletReq...
log.error(ex.getMessage(),ex);
return PitCoinResponse.createErrorResponse(ex);
}
}
** レスポンスの設計 [#m2a7ec34]
APIの呼び出しが,正常に完了した場合もエラーの場合も,''同...
を返すように設計すれば呼び出し側のハンドリングが楽.
レスポンスの設計例
- code: エラーコード
- message: メッセージ
- result: 成功だった場合の結果
例えば,POST /accounts/masa-n/use?amount=250 を実行する際
- 成功 200
{ "code": "OK", "message": null, "result": {"uid": "masa...
- 失敗 404
{ "code": "E14", "message": "Cannot afford 250 coins", "...
ソース
/**
* PitCoinコントローラーが返すレスポンス
*/
@Data
public class PitCoinResponse {
public static final String CODE_OK = "OK";
public static final String ERROR_PREFIX = "E";
public static final int DEFAULT_ERROR_CODE = 99;
/** PitCoin-APIのレスポンスコード */
private String code;
/** メッセージ */
private String message;
/** データ */
private Object result;
/**
* 外部から生成禁止
*/
private PitCoinResponse() {
}
/**
* エラーコード付きでエラーレスポンスを作成する
* @param code エラーコード
* @param ex 例外
* @return エラーレスポンス
*/
public static PitCoinResponse createErrorResponse(int...
PitCoinResponse res = new PitCoinResponse();
res.setCode(ERROR_PREFIX + String.format("%02d", ...
res.setMessage(ex.getMessage());
res.setResult(null);
return res;
}
/**
* デフォルトエラーコードでエラーレスポンスを作成する
* @param ex 例外
* @return エラーレスポンス
*/
public static PitCoinResponse createErrorResponse(Exc...
return createErrorResponse(DEFAULT_ERROR_CODE, ex);
}
/**
* メッセージ付きで成功レスポンスを作成する
* @param data データ
* @param message メッセージ
* @return レスポンス
*/
public static PitCoinResponse createSuccessResponse(O...
PitCoinResponse res = new PitCoinResponse();
res.setCode(CODE_OK);
res.setMessage(message);
res.setResult(data);
return res;
}
/**
* 成功レスポンスを作成する
* @param data データ
* @return レスポンス
*/
public static PitCoinResponse createSuccessResponse(O...
return createSuccessResponse(data, null);
}
}
** 参考文献 [#ob45d9c1]
- 段階的に理解する Java 例外処理
-- https://qiita.com/ts7i/items/d7f6c1cd5a14e55943d4
- Where should I put my exception handling in Spring MVC?
-- https://stackoverflow.com/questions/33966211/where-sho...
- Service and controller layer exception handling design ...
-- https://stackoverflow.com/questions/54677994/service-a...
- Where to handle Exceptions in Spring Applications
-- https://stackoverflow.com/questions/45034371/where-to-...
終了行:
[[SpringBoot]]
* Spring Boot における例外処理 [#qbf253f0]
Spring Bootにおける例外処理のプラクティスを色々調べたので...
** 基本的な考え方 [#ne8afdc9]
+ Javaプログラムにおいて何らかのエラーを検出する際には,...
-- 返り値でエラーを表現してはいけない.
+ 例外は必ず ''非検査例外 (extends RuntimeException)''と...
-- 検査例外(extends Exception)をすると,throws宣言やtry-c...
-- 呼び出し先の検査例外をcatchする必要があれば,非検査例...
try {
//検査例外を投げる呼び出し
} catch(検査例外 e) {
throw new 非検査例外("メッセージ", e); //非検査例外...
}
+ 例外のハンドリングは,専用の ''例外ハンドラ'' で横断的...
+ 例外が発生した場合,Webアプリ・Web-APIの呼び出し元であ...
-- サーバ側で復旧・解決できるエラーについてはこの限りでは...
+ エラーを伝える際には,適切なHTTPステータスをつけられれ...
-- デフォルトではすべて500(Internal Server Error)になって...
** アーキテクチャ [#ebd73770]
&attachref(exception-handling.png);
** 拾うべき例外とその層 [#s76ad8ed]
拾うべきエラーはそれぞれの層で種類が異なる.
Springに関する記事,マニュアルをいろいろと調べた中で,
大体下記のような感じか.
|Springの層|拾うべき例外の種類|例|h
|コントローラ層|サービス利用の前提に違反した例外|フォーム...
|サービス層|ビジネスルールに違反した例外|ユーザIDは他の人...
|レポジトリ層|インフラやシステムの不具合による例外|入出力...
** カスタム例外 [#ubf799e2]
Spring Frameworkが発生させる例外は一般的なものなので,
''アプリケーション固有の例外 (カスタム例外)''でラップして...
|Spring例外|例外発生場所|ラップするカスタム例外|h
|NoSuchElementException|@ServiceでRepository内の口座を参...
|MethodArgumentNotValidException|@RestController でフォー...
例外の種類だけでなく,起こった場所や文脈に応じて,例外に'...
|例外タイプ|検査エラーの内容|エラーコード|h
|不正な操作|フォーム検査にひっかかった|PirCoinValidationE...
|不正な操作|新規ユーザIDが既に存在した|PirCoinValidationE...
|不正な操作|残高以上のお金を出勤しようとした|PirCoinValid...
例外サンプル
/**
* アカウントに対する不正な操作に関する例外
*/
public class AccountValidationException extends RuntimeE...
/** 定義済みエラーコード */
public static final int ACCOUNT_ALREADY_EXISTS = 11;
public static final int INVALID_UID = 12;
public static final int INVALID_AMOUNT = 13;
public static final int INSUFFICIENT_BALANCE = 14;
private static final long serialVersionUID = 1L;
/** エラーコード */
public final int code;
public AccountValidationException(int code, String s...
super(str, cause);
this.code = code;
}
public AccountValidationException(int code, String s...
super(str);
this.code = code;
}
}
使用例
/**
* 与えられたユーザIDの口座からコインを出金する.
*
* @param uid 口座のユーザID
* @param amount 出金する枚数
* @return 出金後の口座情報
*/
public Account useCoin(String uid, int amount) {
if (amount <= 0) {
throw new AccountValidationException(AccountValidation...
"amount must be a positive number");
} else {
int coin = getCoin(uid);
if (coin - amount < 0) { // 残高が0未満にならないかチ...
throw new AccountValidationException(AccountValidatio...
"Cannot afford " + amount + " coins");
} else {
return setCoin(uid, coin - amount);
}
}
}
** 例外のタイプと対応するHTTPステータスコード [#f01f9b3b]
|一般的なJava例外|カスタム例外|対応するHTTPコード|h
|ElementNotFound|そんなリソースないよ例外|404 Not found|
|IllegalArgumentException|そんな引数おかしいよ例外|400 Ba...
|IllegalStateException|アクセスのタイミングがおかしいよ例...
|AuthenticationException|認証失敗したよ例外|401 Unauthori...
|AccessDeniedException,SecurityException|アクセスしては...
|UnsupportedOperationException|そんな操作ないよ例外|501 N...
|Throwable (anything else)|その他の例外|500 Internal Serv...
** 例外ハンドラ [#p77d4ca6]
- @RestControllerに対応する例外ハンドラは,@RestControlle...
- アーキテクチャ的には,例外ハンドラは ''コントローラ層''...
- キャッチする例外の型とその時に返すHTTPステータスコード...
サンプルソース
@RestControllerAdvice
public class PitCoinErrorHandler {
/**
* アカウントが存在しない
* @param req
* @param ex
* @return
*/
@ExceptionHandler(AccountNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public PitCoinResponse handleNotFoundException(HttpSe...
log.warn(ex.getMessage());
return PitCoinResponse.createErrorResponse(ex.cod...
}
/**
* アカウントに対する操作が不正
*/
@ExceptionHandler(AccountValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public PitCoinResponse handleIlleagalArgumentExceptio...
log.warn(ex.getMessage());
return PitCoinResponse.createErrorResponse(ex.cod...
}
/**
* 許可されないメソッドの実行
* @param request
* @param ex
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedExcept...
@ResponseStatus(HttpStatus.FORBIDDEN)
public PitCoinResponse handleMethodNotSupportedExcept...
return PitCoinResponse.createErrorResponse(ex);
}
/**
* 上記で処理していないエラー
*
* @param request
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public PitCoinResponse handleException(HttpServletReq...
log.error(ex.getMessage(),ex);
return PitCoinResponse.createErrorResponse(ex);
}
}
** レスポンスの設計 [#m2a7ec34]
APIの呼び出しが,正常に完了した場合もエラーの場合も,''同...
を返すように設計すれば呼び出し側のハンドリングが楽.
レスポンスの設計例
- code: エラーコード
- message: メッセージ
- result: 成功だった場合の結果
例えば,POST /accounts/masa-n/use?amount=250 を実行する際
- 成功 200
{ "code": "OK", "message": null, "result": {"uid": "masa...
- 失敗 404
{ "code": "E14", "message": "Cannot afford 250 coins", "...
ソース
/**
* PitCoinコントローラーが返すレスポンス
*/
@Data
public class PitCoinResponse {
public static final String CODE_OK = "OK";
public static final String ERROR_PREFIX = "E";
public static final int DEFAULT_ERROR_CODE = 99;
/** PitCoin-APIのレスポンスコード */
private String code;
/** メッセージ */
private String message;
/** データ */
private Object result;
/**
* 外部から生成禁止
*/
private PitCoinResponse() {
}
/**
* エラーコード付きでエラーレスポンスを作成する
* @param code エラーコード
* @param ex 例外
* @return エラーレスポンス
*/
public static PitCoinResponse createErrorResponse(int...
PitCoinResponse res = new PitCoinResponse();
res.setCode(ERROR_PREFIX + String.format("%02d", ...
res.setMessage(ex.getMessage());
res.setResult(null);
return res;
}
/**
* デフォルトエラーコードでエラーレスポンスを作成する
* @param ex 例外
* @return エラーレスポンス
*/
public static PitCoinResponse createErrorResponse(Exc...
return createErrorResponse(DEFAULT_ERROR_CODE, ex);
}
/**
* メッセージ付きで成功レスポンスを作成する
* @param data データ
* @param message メッセージ
* @return レスポンス
*/
public static PitCoinResponse createSuccessResponse(O...
PitCoinResponse res = new PitCoinResponse();
res.setCode(CODE_OK);
res.setMessage(message);
res.setResult(data);
return res;
}
/**
* 成功レスポンスを作成する
* @param data データ
* @return レスポンス
*/
public static PitCoinResponse createSuccessResponse(O...
return createSuccessResponse(data, null);
}
}
** 参考文献 [#ob45d9c1]
- 段階的に理解する Java 例外処理
-- https://qiita.com/ts7i/items/d7f6c1cd5a14e55943d4
- Where should I put my exception handling in Spring MVC?
-- https://stackoverflow.com/questions/33966211/where-sho...
- Service and controller layer exception handling design ...
-- https://stackoverflow.com/questions/54677994/service-a...
- Where to handle Exceptions in Spring Applications
-- https://stackoverflow.com/questions/45034371/where-to-...
ページ名: