@ModelAttribute
를 통해 요청값을 입력받는 DTO를 만들었다.@Getter
@Setter
public class RequestDTO {
private ShareType type;
private List<MultipartFile> images;
private int price;
public RequestDTO(ShareType type, List<MultipartFile> images, int price) {
this.type = type;
this.images = images;
this.price = price;
}
}
type
, price
필드는 클라이언트가 입력하지 않으면 null 값이 할당되어 @NotNull
어노테이션을 통해 Bean Validation
이 동작하도록 구현했다.price
필드는 Primitive Type
이기 때문에 null 값이 들어갈 수 없으므로 Wrapper Class
인 Integer
로 타입을 변경해줬다.@Getter
@Setter
public class RequestDTO {
@NotNull(message = "요청한 쉐어정보 값이 비어있습니다.")
private ShareType type;
@NotEmpty(message = "요청한 쉐어정보 값이 비어있습니다.")
private List<MultipartFile> images;
@NotNull(message = "요청한 쉐어정보 값이 비어있습니다.")
private Integer price;
public RequestDTO(ShareType type, List<MultipartFile> images, int price) {
this.type = type;
this.images = images;
this.price = price;
}
}
price
필드의 값을 넣지 않고 테스트를 실행해보니 아래와 같은 예외(TypeMismatchException)가 발생했다.int
타입인 price
필드에 null
값을 할당할 수 없다는 뜻으로 이해했다.@ModelAttribute
의 동작 방식이 자바빈 프로퍼티 방식으로만 필드에 값을 할당해주는 걸로 알고 있었기 때문에 이 에러가 발생한게 조금 의아했다. RequestDTO
클래스는 Lombok의 @Setter
를 통해서 setPrice
메서드를 만들어 줄 것이고 setPrice
메서드의 매개변수 타입은 price 필드의 타입과 같은 Integer
라고 생각했기 때문에 도대체 저 int 타입은 어디서 나온거지…?
라는 궁금증이 생겼다.TypeMismatchException
이 발생하는지 확인해봤다.ModelAttributeMethodProcessor
클래스에서 createAttribute
메서드를 호출하고 BindException
이 발생한 것을 확인할 수 있었다.createAttribute
메서드의 시작 부분에 break point를 찍고 다시 요청해봤다.createAttribute
메서드의 219번째 줄에서 getResolvableConstructor
메서드를 통해 ModelAttribute로 생성할 객체의 생성자를 찾아서 ctor
변수에 할당한다.ModelAttribute
로 생성할 객체의 생성자 정보를 담고 있는 변수 ctor
의 정보를 디버거로 확인해봤다.int
인 것을 확인할 수 있다.price
필드 타입은 분명 Integer
로 변경했는데 생각해보니까 생성자의 매개변수 타입은 따로 변경해주지 않아서 int
타입이었던 것이다.null
값으로 매개변수를 채워준 것일까?int
타입인 생성자의 매개변수 이름(price
)와 동일한 파라메터를 클라이언트가 입력하지 않는다면 스프링이 null
값으로 매개변수를 넣어줘서 TypeMissMatchException
이 발생한 것이다.price
도 Integer
타입으로 변경해줬다.@Getter
@Setter
public class RequestDTO {
@NotNull(message = "요청한 쉐어정보 값이 비어있습니다.")
private ShareType type;
@NotEmpty(message = "요청한 쉐어정보 값이 비어있습니다.")
private List<MultipartFile> images;
@NotNull(message = "요청한 쉐어정보 값이 비어있습니다.")
private Integer price;
public RequestDTO(ShareType type, List<MultipartFile> images, Integer price) {
this.type = type;
this.images = images;
this.price = price;
}
}
price
필드를 입력하지 않아도 TypeMismatchException
이 발생하지 않고 예외 메시지를 보내줄 수 있다!기본 생성자와 setter
를 통해서 ModelAttribute가 동작한다고 생각했지만 스프링은 생성자의 매개변수 이름과 같은 파라메터 값을 ModelAttribute의 대상인 객체에게 할당해 주는 것을 알 수 있었다.Primitive Type
이여서 TypeMismatchException
이 발생했던 것이다.@AllArgsConstructor
만 있어도 필드에 값이 제대로 들어가는 경우도 있었고 기본 생성자의 접근 제어자와 setter의 유무에 따라서 필드에 값이 제대로 들어가지 않는 경우도 있었다.ModelAttribute
의 동작 방식에 대해 제대로 학습해봐야겠다.