웹 서비스는 폼 입력시 오류가 발생하면, 데이터를 유지한 상태로 오류를 사용자에게 알려주어야 한다.
컨트롤러의 중요한 역활 중 하나는, HTTP 요청이 정상인지 검증하는 것이다
이전 프로젝트를 일부 수정한 validation-start프로젝트에서 진행
고객이 폼을 잘못 입력한 경우
검증시 오류가 발생하면 errors 해시맵에 담아놓고, 이와함께 폼으로 다시가는식
@ModelAttribute는 사용자가 입력한 데이터를 계속 가지고 있음
폼에서 사용자에게 오류 알리기
<div th:if="${errors?.containsKey('globalError')}">
<p class="field-error" th:text="${errors['globalError']}">전체 오류 메시지</p>
</div>
errors? : 에러가 null이면, NPE대신 null 반환th:if에서 null을 실패로 처리하고 무시 (SpringEL문법)스프링이 제공하는 검증 오류를 보관하는 객체
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
주의
BindingResult bindingResult는 무조건 ModelAttribute Item item 뒤어야함
FieldError(String objectName, String field, String defaultmessage)@ModelAttribute 이름, 필드이름, 오류 기본메시지ObjectError(String objectName, String defaultMessage)@ModelAttribute 이름, 오류 기본메시지 타임리프를 통한 처리
ObjectError(글로벌에러)
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>
FieldError
th:field로 설정한 필드와, bindingResult의 필드가 일치할 때 오류
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}"
th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요">
<div class="field-error" th:errors="*{itemName}">
상품명 오류
</div>
</div>
<div>
기능정리
#fields : BindingResult가 제공하는 검증 오류에 접근 가능th:errors : 해당 필드에 오류가 있는 경우 태그 출력 th:if편의버전th:errorclass : 해당 필드에 오류가 있으면 클래스정보 추가@ModelAttribute에 바인딩 시 오류가 발생하면?
BindingResult가 있으면 -> 400오류가 발생하면서 오류페이지로 이동BindingResult가 없으면 ->오류정보(FieldError)를 BindingResult 에 담아서 컨트롤러를 호출BindingResult에 검증 오류를 적용하는 3가지방법
FieldError를 BindingResult에 넣어줌Validator사용BindingResult는 무조건 검증할 대상 다음에 와야하고, 모델에 자동으로 포함된다
FieldError 생성자(2개있음)
ObjectName : 객체 이름field : 오류 필드rejectedValue : 사용자가 입력한 값(거절된)bindingFailure : 바인딩 실패인지, 검증 실패인지 구분codes : 메시지 코드arguments : 메시지에서 사용하는 인자defaultMessage : 기본 오류 메시지new FieldError("item","itemName",item.getItemName(), false,null,null,"상품 이름은 필수입니다."
타입 오류로 바인딩에 실패하면
스프링은 FieldError를 생성하면서 입력값을 넣고,
bindingResult에 사용자 입력 데이터를 담아 컨트롤러 호출
타임리프에서 입력 값 유지
th:field="*{price}"
th:field는 정상일땐 모델 객체의 값을 사용하지만, 오류가 발생하면 FieldError에서 보관한 값을 사용해 출력
errors 메시지파일
여기서 했던 메시지처럼, errors.properties라는 별도의 파일 생성 후
spring.messages.basename=messages,errors 추가하면
resources 밑에 있는 errors.properties를 참고하게됨
ex)
if(!StringUtils.hasText(item.getItemName())){
bindingResult.addError(new FieldError("item","itemName",item.getItemName(), false,
new String[]{"required.item.itemName"},null,"상품 이름은 필수입니다.")); }
codes에 String 배열로 넣어주면 된다.
배열로 받는 이유는, 첫 없으면 다음 원소를 넣어줘서임
argument가 있으면, Object배열로 넣어주면 된다.
bindingResult는 검증할 객체 바로 다음에 오기 때문에, 검증할 객체를 알고있다.
rejectValue(),reject()BindingResult가 제공하는 rejectValue(),reject()로
FieldError,ObjectError를 직접 생성하지 않고 오류를 다룰 수 있다.
바꾼 방식
bindingResult.rejectValue("itemName","required",
new Object[]{1000,1000000},null);
////
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
//오류필드명,messageResolver를 위한 오류 코드, Args,기본메시지
오류 메시지를 required=필수값 입니다 처럼 단순하게 만들면 범용성이 좋지만, 세밀한 메시지를 작성하기 어렵다.
반대로
required.item.itemName=상품 이름은 필수입니다. 처럼 너무 자세하게 만들면 범용성이 떨어진다.
따라서 범용성으로 사용하다가, 세밀하게 작성해야 하면 세밀한 내용이 적용되도록 단계를 둔다
스프링은 MessageCodesResolver 로 이러한 기능을 지원한다.
MessageCodesResolver
DefaultMessageCodesResolverObjectError,FieldError와 사용DefaultMessageCodesResolver의 기본 메시지 생성 규칙
객체 오류
1.:code+","+object name
2.:code
ex) 에러코드가 totalPriceMin objectName이 item이라면
totalPriceMin.item (1순위) => 없으면 totalPriceMin(2순위)
필드 오류
1.:code+"."+object name + "." + field
2.:code + "." + field
3. code+ "." + field type
4. code
ex) 에러코드가 required objectName이itemName이라면
1. "required.item.itemName"
2. "required.itemName"
3. "required.java.lang.String"
4. "required"
오류 메시지 출력
타임리프가 랜더링 할때 오류가 있다면 th:errors가 생성된 메시지 코드들을 순서대로 돌아가면서 메시지를 찾고 출력 (없으면 default->에러남)
정리
1. rejectValue() 호출
2. MessageCodesResolver를 사용해서 검증 오류 코드로 메시지 코드 생성
3.new FieldError()를 생성하면서 메시지 코드들을 보관
4.th:errors에서 순서대로 메시지를 찾음
검증 오류 코드는 2가지로 나뉨
rejectValue() 직접 호출스프링은 타입 오류가 발생하면 typeMismatch라는 오류 코드를 생성
=> 이게 MessageCodesResolver를 통하면서 4가지 메시지 코드 생성됨
errors.properties에 메시지 코드를 정의하면 됨
ex)
typeMismatch.java.lang.Integer=숫자를 입력해주세요.
typeMismatch=타입 오류입니다.
컨트롤러에서 검증하지않고, 따로 Validator를 만들어서 검증!
스프링에서 제공하는 Validator를 구현
오버라이딩
public boolean supports(Class<?? class)return Item.class.isAssignableFrom(clazz);원하는 객체인지bindingResult컨트롤러에서 WebDataBinder를 통해서 검증기를 추가하고
메소드에서 @Validated를 통해서 검증기를 실행할 수 있다.