김영한님의 Spring MVC 2편 Validation 강의를 듣다가 구조가 헷갈려 확실히 이해하고자 정리하게 되었다.
1. Intro
서비스를 개발하는데 있어서 검증은 매우 중요한 개념이다.
예를들어서, 온라인 쇼핑 서비스에 사용자의 잔액을 검증하지도 않고 그냥 주문을 받아버린다면 엄청난 오류가 발생할 것이다.
Spring Boot는 검증 작업에 대해서 매우 편리한 솔루션을 제공해준다.
2. BindingResult
BindingResult는 스프링부트가 컨트롤러에서 요청받은 객체를 바인딩 하는데 발생하는 오류를 포착해주는 역할을 한다.
BindingResult는 주로 컨트롤러의 메서드 파라미터 내에 @ModelAttribute 뒤에 위치해야 하며 그 사이에 다른 파라미터가 오는 건 상관없다.
이 BindingResult는 오류는 담아놓는 저장소라고 생각하면 편하다.
개발자는 이 BindingResult에 원하는 검증 로직을 넣을 수 있다.
이때 사용하는 메서드가 addError()다.
// 기본적인 파라미터
// 특정 필드에 대한 검증
if(!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수입니다."));
}
// 특정 필드가 아닌 복합 룰 검증
if(item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice < 10000) {
bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = "+ resultPrice));
}
}
// 추가적인 파라미터
// 특정 필드에 대한 검증
if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.addError(new FieldError("item", "price", item.getPrice(), false,
new String[]{"range.item.price"}, new Object[]{1000, 1000000}, null));
}
// 특정 필드가 아닌 복합 룰 검증
if(item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice < 10000) {
bindingResult.addError(new ObjectError("item", new String[]{"totalPriceMin"},
new Object[]{10000, resultPrice}, null));
}
}
검증 종류에 따라 addError()안에 들어가는 객체가 달라진다.
- 특정 필드에 대한 검증: FieldError
- 복합적인 검증: ObjectError
또한 파라미터 또한 기본적인 정보를 넣는 버전이 있고 추가적인 정보를 넣는 버전이 있다.
- 기본적인 파라미터
- FieldError(String objectName, String field, String defaultMessage)
- ObjectError(String objectName, String defaultMessage)
1. ObjectName: @ModelAtrribute 이름
2. field: 오류가 발생한 필드
3. defaultMessage: 오류 기본 메시지
- 추가적인 파라미터
- FieldError( String objectName, String field, @Nullable Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage )
- Object( String objectName, @Nullable Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage)
ObjectName, field, defaultMessage는 위와 동일하다.
1. rejectedValue: 사용자가 잘못 입력한 값
2. bindingFailure: 바인딩 실패여부
3. codes: properties파일에 지정한 메시지 코드 값들 여러 개를 지정할 수 있으며 배열의 순서대로 우선권을 가진다.
ex)[required.item.itemName, required.default]로 넣으면 itemName부터 읽고 그게 없으면 default를 사용함
4. arguments: 메시지 코드 값의 파라미터들
이 파라미터들은 분명 유용하게 사용할 수 있지만 너무 많기때문에 코드가 지저분하다는 생각이 든다.
스프링은 이 파라미터들을 더욱 깔끔하게 사용할 수 있는 방법을 제공한다.
3. rejectValue(), reject()
rejectValue(), reject()가 바로 FieldError(), ObjectError()를 깔끔하게 사용하게 해주는 메서드들이다.
// 2. rejectValue(), reject() 사용
// 특정 필드에 대한 검증
if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000}, null);
}
// 특정 필드가 아닌 복합 룰 검증
if(item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
위에서 addError()를 사용했던 코드를 위와 같이 치환할 수 있다.
상당히 코드가 깔끔해졌다
근데 한가지 의문이 생긴다.
분명 properties 파일에선 range.item.price로 지정해줬는데 여기선 range만 넣어줬다.
이게 어떻게 가능할까?
바로 MessageCodesResolver 때문이다.
4. MessageCodesResolver
MessageCodesResolver는 rejectValue(), reject()에서 넣어줬던 errorCode정보를 기반으로 오류메시지코드를 생성해준다.
우리는 위에 addError()를 사용 할 때 new String[]{...}으로 직접 메시지 코드 값을 넣어줬다.
MessageCodeResolver는 이 메시지코드를 넣어주는 역할이라고 이해하면 된다.
그 후엔 똑같이 이 codes를 보고 순서대로 있는 메시지를 찾아준다.
이 코드들은 일정한 규칙을 갖고 생성된다.
- rejectValue("필드", "코드")
- 코드.객체.필드
- 코드.필드.
- 코드.필드의 타입
- 코드
- reject("코드")
- 코드.객체이름
- 코드
동작을 그림으로 표현해보면 다음과 같다.
'Spring > Spring MVC' 카테고리의 다른 글
Spring MVC의 구조 - HandlerMapping과 HandlerAdapter 동작 방식 (0) | 2024.03.09 |
---|