読者です 読者をやめる 読者になる 読者になる

なみひらブログ

学んだことを日々記録する。~ since 2012/06/24 ~

SpringFrameworkでのJSON Validationの実装/動作確認メモ

API Java SpringFramework プログラミング

背景

SpringFrameworkでAPIを実装するときにリクエスト内容の妥当性確認(Validation)をよく行うので、その実装パターン/挙動をメモっときます。
今回は、ユーザ情報をPOSTする際に妥当性を検証するAPIを題材にしています。

事前準備

必要なライブラリを入れる

必要なライブラリは以下の通り

実装パターン

リクエストを表すクラスを作る

  • UserDto.java
    • 特記事項
      • 必要そうなパラメータを用意した単純なPOJO
      • "userId"については、「nullがダメ」「最大文字数16文字」という条件を付与する。(深い意味はありません(;´Д`))
      • ※@Dataはlombokのアノテーション。setter/getter/toStringなどなどをコンパイル時に自動生成してくれます。
/**
 * 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
    • 特記事項
      • リクエストを受け取ってDBにinsertするクラス
      • @RequestBody・・・引数にこのアノテーションを入れると、リクエストボディが渡される。
      • @Valid・・・引数にこのアノテーションを入れると、リクエストボディについて上記のPOJOにつけた妥当性検証が行われる。
      • 継承しているAbstractApiControllerについては後述。
/**
 * 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・・・妥当性検証でエラーが発生した際に投げられる例外。
      • @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]] 

まとめ

どんなValidationエラーが起こったかをもっとさくっと判定できるようになりたい(;´Д`)

参考