[Spring] BindResult 객체
BindResult는 Controller 메소드에서 @ModelAttribute에서 객체 바인딩 시 타입 검증에 실패하면 에러정보를 담는 인터페이스이다.
사용예시를 보면,
Item.java
import lombok.Data;
@Data
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
Controller.java
@PostMapping("/add")
public String addItem3(@ModelAttribute Item item, BindingResult bindingResult, Model model) {
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for (FieldError error:fieldErrors) {
log.info("error={}", error);
}
/// ...
}
⭐️⭐️ BindingResult는 @ModelAttribute 바로 뒤에 나와야 한다.
http 요청
content-type: multipart/form-data
message body: itemName=iteam1&price=abc&quantity=3
결과 (로그)
error=Field error in object 'item' on field 'price': rejected value [abc];
codes [typeMismatch.item.price,typeMismatch.price,typeMismatch.java.lang.Integer,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [item.price,price];
arguments [];
default message [price]];
default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Integer' for property 'price';
nested exception is java.lang.NumberFormatException: For input string: "abc"]
로그를 보면 어떤 객체(Item)의 어떤 필드(price)가 어떠한 이유(typeMismatich)로 오류가 발생했는지 알 수 있고
어떤 값을 바인딩 하려고 시도했는지도 볼 수 있다.
이렇게 나올 수 있는 이유는 스프링이 @ModelAttribute의 객체에 타입 오류 등으로 바인딩에 실패했을 경우
FieldError를 생성해서 BindingResult에 넣어줬기 때문이다.
여기서 FieldError을 잠깐 살펴볼까?
public class FieldError extends ObjectError {
// 생성자 1
public FieldError(String objectName, String field, String defaultMessage) {
this(objectName, field, null, false, null, null, defaultMessage);
}
// 생성자 2
public FieldError(String objectName, String field, @Nullable Object rejectedValue, boolean bindingFailure,
@Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage) {
super(objectName, codes, arguments, defaultMessage);
Assert.notNull(field, "Field must not be null");
this.field = field;
this.rejectedValue = rejectedValue;
this.bindingFailure = bindingFailure;
}
}
- objectName: @ModelAttribute 이름
- field: 오류가 발생한 필드 이름
- defaultMessage: 오류 기본 메세지
- rejectedValue: 거절된 값
- bindingFailure: 타입 오류 실패인지, 검증 실패인지
- codes: 메시지 코드
- arguments: 메세지에서 사용하는 인자
그리고 이렇게 bindingResult가 만들어지면 자동으로 Model에 담겨져서 View로 넘어간다.
이것도 확인해볼까?
Controller.java
@PostMapping("/add")
public String addItem(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes,Model model) {
if (bindingResult.hasErrors()) {
Object result = model.getAttribute("org.springframework.validation.BindingResult.item");
log.info("result={}", result);
}
// ...
}
결과 (로그)
result=org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'item' on field 'price': arguments ....(생략)
BeanPropertyBindingResult라는 객체가 model에 담겨있는 것을 확인할 수 있다.
BeanPropertyBindingResult는 인터페이스인 BindingResult를 구현한 구현체이다.
(이미지 출처: https://fordevelop.tistory.com/115)
ref