CLOVER🍀

That was when it all began.

SpringのRestControllerとBindingResultと

RestControllerとBean Validationでの、ちょっとした動作確認。

リクエストをマッピングするこんなクラスと
src/main/java/org/littlewings/spring/restvalidate/ParamBean.java

package org.littlewings.spring.restvalidate;

import javax.validation.constraints.Digits;

public class ParamBean {
    @Digits(integer = 5, fraction = 0)
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

こういうRestControllerを用意。
src/main/java/org/littlewings/spring/restvalidate/TestController.java

package org.littlewings.spring.restvalidate;

import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @RequestMapping(value = "validOnly", method = RequestMethod.POST)
    public String validOnly(@RequestBody @Validated ParamBean bean) {
        return "OK!";
    }

    @RequestMapping(value = "validWithBindingResult", method = RequestMethod.POST)
    public String validWithBindingResult(@RequestBody @Validated ParamBean bean, BindingResult bindingResult) {
        if (!bindingResult.hasErrors()) {
            return "OK!";
        } else {
            return "NG!";
        }
    }
}

両方ともリクエストのバリデーションは行いますが、片方は@Validatedアノテーションを付けるのみ、もう片方はBindingResultを受け取るように実装。

違いが出るのか?ということで、確認してみます。

@Validatedアノテーションのみの場合から。バリデーションOKの時。

$ curl -XPOST -i -H 'Content-Type: application/json' http://localhost:8080/validOnly -d '{ "value": 100 }'
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=UTF-8
Content-Length: 3
Date: Thu, 01 Oct 2015 15:19:50 GMT

OK!

NGの時。

$ curl -XPOST -i -H 'Content-Type: application/json' http://localhost:8080/validOnly -d '{ "value": "abc" }'
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 01 Oct 2015 15:20:05 GMT
Connection: close

{"timestamp":1443712805359,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public java.lang.String org.littlewings.spring.restvalidate.TestController.validOnly(org.littlewings.spring.restvalidate.ParamBean), with 1 error(s): [Field error in object 'paramBean' on field 'value': rejected value [abc]; codes [Digits.paramBean.value,Digits.value,Digits.java.lang.String,Digits]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [paramBean.value,value]; arguments []; default message [value],0,5]; default message [numeric value out of bounds (<5 digits>.<0 digits> expected)]] ","path":"/validOnly"}

例外が返ってきて、HTTPステータスコードは400になりますよっと。

今度は、@Validated+BindingResultの場合。バリデーションOKの時。

$ curl -XPOST -i -H 'Content-Type: application/json' http://localhost:8080/validWithBindingResult -d '{ "value": 100 }'HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=UTF-8
Content-Length: 3
Date: Thu, 01 Oct 2015 15:20:51 GMT

OK!

NGの時。

$ curl -XPOST -i -H 'Content-Type: application/json' http://localhost:8080/validWithBindingResult -d '{ "value": "abc" }'
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=UTF-8
Content-Length: 3
Date: Thu, 01 Oct 2015 15:21:30 GMT

NG!

というわけで、BindingResultを受けとるようにするとメソッド内に制御が移り、なおかつHTTPステータスコードは特に何も操作しなければ200になりました。

追記
コメントで指摘をいただきましたが、RestControllerの場合は通常@ExceptionHandlerでハンドリングするものらしいです。
Validating Spring REST controllers' beans using the Bean Validation API... and writing the tests for it! - Codes And Notes
5.16. RESTful Web Service — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.0.1.RELEASE documentation