인프런 김영한님의 스프링 MVC 2편을 듣고, 정리 겸 복습한 내용입니다.
컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것
클라이언트 검증, 서버 검증
BindingResult
코드
public String addItem(@ModelAttribute Item item,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
//필드에러의 경우
bindingResult.addError(new FieldError("item", "itemName", "아이템/네임 필드에러"));
//글로벌에러의 경우
bindingResult.addError(new ObjectError("item", "아이템 글로벌에러"));
}
FieldError
public FieldError(String objectName, String field, String defaultMessage);
public FieldError(String objectName, String field,
@Nullable Object rejectedValue,
boolean bindingFailure,
@Nullable String[] codes,
Nullable Object[] arguments,
@Nullable String defaultMessage)
ObjectError
public ObjectError(String objectName, String defaultMessage) {}
BindingResult 파라미터의 위치는 확인할 클래스 바로 다음에 와야한다.
스프링이 제공하는 검증 오류 보관 객체
BindingResult가 있으면 @ModelAttribute데이터 바인딩시 오류가 발생해도 컨트롤러 호출
BindingResult는 Errors 인터페이스 상속
실제 넘어오는 구현체는 BeanPropertyBindingResult
스프링 부트 메시지 설정추가
application.properties
string.messages.basename=message,errors
#message.properties, errors.properties 두개의 프로퍼티를 인식
errors.properties
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
FieldError 두번째 생성자 사용법
코드
new FieldError("item", "price", item.getPrice(), false, new String[] {"range.item.price"}, new Object[]{1000, 1000000}
배열을 이용하여 메시지코드를 지정
위의 코드 실행시 range.item.price=가격은 1000 ~ 10000 까지 허용합니다. 에러메시지 확인 가능
너무 사용하기 어렵고 번거롭다.
이때 BindingResult가 제공하는 rejectValue(), reject()를 사용할 수 있다.
rejectValue() , reject()
rejectValue(): 코드
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
//사용
bindingResult.rejectValue("quantity", "max", new Object[]{9999}, null);
reject(): 코드
void reject(String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
//사용
bindingResult.reject("totalPriceMin", new Object[]{10000,resultPrice}, null);
field: 오류
errorCode: 오류코드 ( messageResolver 를 위한 오류 코드)
errorArgs: 메시지 인자
defaultMessage: 기본 메시지
BindingResult는 대상이 되는 객체 바로 뒤에 오므로 대상을 알고 있다.
오류 코드와 메시지 처리
required.item.itemName: 상품 이름은 필수 입니다.
range.item.price: 상품의 가격 범위 오류입니다.
//또는 단순하게
required: 필수 값입니다.
range: 범위 오류 입니다.
MessageCodesResolver
객체오류
다음 순서로 2가지 생성
1.code + "." + object name
2.code
ex) 오류 코드: required, object name: item
1.required.item
2.required
필드오류
1.code + "." + object name + "." field name
2.code + "." + field name
3.code + "." + field type
4.code
ex) 오류 코드: typeMismatch, object name: user, filed name: age, filed type: int
1.typeMismatch.user.age
2.typeMismatch.age
3.typeMismatch.int
4.typeMismatch
동작 방식
rejectValue(), reject()는 내부에서 MessageCodesResolver를 사용, 여기서 메시지 코드 생성
FiledError, ObjectError 생성자를 보면 오류코드를 하나가 아니라 배열로 받음
MessageCodesResolver를 통해서 생성된 순서대로 오류 코드를 보관
생성된 메시지를 순서대로 찾아서 활용가능
범용성있는 에러의 경우 덜 구체적으로, 특별한경우 구체적으로 적어 관리할 수 있다.
Validator
컨트롤러에서 검증로직을 분리하기 위하여 도입
스프링은 검증을 체계적으로 제공하기 위해 다음 인터페이스를 제공한다.
public interface Validator {
//해당 검증기를 지원하는 여부 확인
boolean supports(Class<?> clazz);
//검증 대상 객체와 BindingResult
void validate(Object target, Errors errors);
}
활용
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Item item = (Item) target;
if(...검증로직) {
//통과못할경우
errors.rejectValue("price","range",new Object...,null);
}
}
}
public String addItemV5(@ModelAttribute Item item,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
//검증
itemValidator.validate(item, bindingResult);
//에러가 있으면
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v2/addForm";
}
//성공 로직
...
}
WebDataBinder
@InitBinder
pulbic void init(WebDataBinder dataBinder) {
dataBinder.addValidators(itemValidator);
}
@Validated
public String addItem(@Validated @ModelAttribute Item item, BindingResult
bindingResult, RedirectAttributes redirectAttributes) {}
BeanValidation
사용예시
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min=1000, max=100000)
private Integer price;
@Max(9999)
private Integer quantity;
}
BeanValidation은 특정 구현체가 아니라 Bean Validation 2.0(JSR-380)이라는 기술 표준이다.
BeanValidation을 구현한 기술중 일반적으로 하이버네이트 벨리데이터 구현체를 사용한다.
의존관계 추가
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
스프링 MVC가 BeanValidator를 사용하는 방법
BeanValidation 에러코드
NotBlank.item.itemName
NotBlank.itemName
NotBlank.java.lang.String
NotBlank
...
BeanValidation 오브젝트 오류
@Data
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000")
public class Item {
//...
}
위와 같이 사용하지만 실무에서는 활용가능성이 적어보인다.
오브젝트 오류의 경우 자바에서 직접작성
한계
@NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
private String itemName;
public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item ...){}
@Valid , @Validated 는 HttpMessageConverter ( @RequestBody )에도 적용할 수 있다.
@ModelAttribute
@RequestBody