SpringFrameworkでのJSON Validationの実装/動作確認メモ
背景
SpringFrameworkでAPIを実装するときにリクエスト内容の妥当性確認(Validation)をよく行うので、その実装パターン/挙動をメモっときます。今回は、ユーザ情報をPOSTする際に妥当性を検証するAPIを題材にしています。
事前準備
必要なライブラリを入れる
必要なライブラリは以下の通り- Maven Repository: javax.validation » validation-api(API)
- Maven Repository: org.hibernate » hibernate-validator(参照実装)
実装パターン
リクエストを表すクラスを作る
- UserDto.java
/** * User Dto */ @Data public class UserDto { private int id; @NotNull @Size(max=16) private String userId; private String name; private String password; private UUID uuid; }
リクエストを受け取るAPIを実装する
- UserApiController.java
/** * user api controller */ @RestController public class UserApiController extends AbstractApiController { @Autowired private UserDao userDao; @RequestMapping(value = "/api/users", method = RequestMethod.POST) @Transactional(rollbackFor = Throwable.class) public UserDto post(@RequestBody @Valid UserDto dto){ userDao.insert(dto); return dto; } }
例外を処理するクラスを実装する
- AbstractApiController.java
- 特記事項
- 妥当性検証でエラーになった場合、そのままだとレスポンスにスタックトレースが返却される(ステータスコード500で)ので、例外をハンドリングする処理が必要。
- 各コントローラでハンドリングすると大変なので(=入力エラーはどのコントローラでも同じ処理)、親クラスでまとめて処理するほうが楽。
- @ExceptionHandler・・・コントローラ内で例外が発生したときに呼ぶメソッドを決めるアノテーション。この場合は「MethodArgumentNotValidException」が投げられた場合にこのメソッドが呼ばれる。
- MethodArgumentNotValidException・・・妥当性検証でエラーが発生した際に投げられる例外。
- MethodArgumentNotValidException (Spring Framework 4.2.1.RELEASE API)
- 実際にどんなエラーになったかはgetBindingResult()で分かる。
- @ResponseStatus・・・返却するHTTPステータスコード。200以外の場合に指定する。
- ※とりあえずのエラーの型(ErrorInfo:独自クラス)で返している(;´Д`)本当はAPI仕様にそった整形が必要。
- 特記事項
/** * API関連のコントローラの抽象クラス */ public abstract class AbstractApiController { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) private ErrorInfo handleException(MethodArgumentNotValidException e){ return new ErrorInfo(e.getMessage()); } }
挙動/動作
正常系- リクエスト例
{ "userId" : "namihira", "name" : "namihira", "password" : "hoge" }
- レスポンス例
{ "id": 0, "userId": "namihira", "name": "namihira", "password": "hoge", "uuid": "ebd384a9-394f-4b19-b2f9-6db765c3e9c2" }
例外系:@NotNullでエラーになる場合
- リクエスト例
{ "name" : "namihira", "password" : "hoge" }
- MethodArgumentNotValidExceptionを出力してみる(System.out.println(e))
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument at index 0 in method: public jp.co.namihira.app.integration.dto.UserDto jp.co.namihira.app.web.controller.api.UserApiController.post(jp.co.namihira.app.integration.dto.UserDto), with 1 error(s): [Field error in object 'userDto' on field 'userId': rejected value [null]; codes [NotNull.userDto.userId,NotNull.userId,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDto.userId,userId]; arguments []; default message [userId]]; default message [may not be null]]
例外系:@Sizeでエラーになる場合
- リクエスト例
{ "userId" : "12345678901234567", "name" : "namihira", "password" : "hoge" }
- MethodArgumentNotValidExceptionを出力してみる(System.out.println(e))
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument at index 0 in method: public jp.co.namihira.app.integration.dto.UserDto jp.co.namihira.app.web.controller.api.UserApiController.post(jp.co.namihira.app.integration.dto.UserDto), with 1 error(s): [Field error in object 'userDto' on field 'userId': rejected value [12345678901234567]; codes [Size.userDto.userId,Size.userId,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDto.userId,userId]; arguments []; default message [userId],16,0]; default message [size must be between 0 and 16]]