[도입]
컨트롤러의 역할 중 하나는 HTTP 요청이 정상인지 검증하는 것이다. 이 때 검증은 클라이언트와 서버단에서 모두 이루어 지는게 좋다. 그 이유는 다음과 같다.
- 클라이언트 검증은 조작할 수 있으므로 보완에 취약하다. -> 데이터를 조작해서 서버로 보낼 수 있다.
- 서버만으로 검증하면, 즉각적인 고객 사용성이 부족해진다.
따라서 둘을 적절히 섞어서 사용하되, 최종적으로 서버 검증은 필수적이다. Spring에서는 서버단에서 검증을 할 때 에러를 확인하고 이를 view로 쉽게 전달하기 위해서 BindingResult객체를 제공한다.
[BindingResult]
보통 errors 메세지를 담고 활용하기 위해 HashMap을 이용하는데, 스프링은 이를 더 쉽게 처리하기 위해 BindingResult객체를 제공한다. BindingResult는 model에 자동으로 포함되며,검증 오류를 보관하는 객체이다.
1. BindingResult는 @ModelAttribute 파라미터 바로 옆에 위치해야한다.
@PostMapping
public String requestLogin(
@ModelAttribute("signInForm") LoginForm form,
BindingResult bindingResult) {}
2. 필드에 오류가 있으면 FieldError 객체를 생성해서 bindingResult에 담아두면 된다.
if(userService.findByEmail(form.getEmail()).isEmpty()) {
bindingResult.addError(new FieldError("signInForm", "email", "존재하지 않는 이메일입니다."));
return "redirect:/loginForm";
}
3. 필드 오류가 아닌 글로벌 오류는 ObjectError를 생성한다.
bindingResult.addError(new ObjectError("user", "message"));
4. 타임리프는 스프링의 BindingResult를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다. "#fields"
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}"
th:text="${err}">글로벌 오류 메시지
</p>
</div>
5. 해당 필드에 오류가 있으면 th:errors에 출력한다.
<div class="field-error" th:errors="*{email}">
이메일 오류
</div>
@ModelAttribute와 BindingResult
BindingResult가 있으면 @ModelAttribute에 데이터 바인딩 시 오류가 발생해도 컨트롤러가 호출된다.
Ex ) @ModelAttribute에 바인딩 시 타입 오류가 발생하면 BindingResult가 있으면 오류 정보를 bindingResult에 담아 컨트롤러를 정상 호출하지만, 없으면 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.
FieldError 생성자
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 defualtMessage)
- objectName : 오류가 발생한 객체 이름
- field : 오류 필드
- rejectedValue : 사용자가 입력한 값 (거절된 값)
- bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
- codes : 메세지 코드
- arguments : 메세지에서 사용하는 인자
- defaultMessage : 기본 오류 메세지
사용자의 입력 데이터가 컨트롤러의 @ModelAttribute에 바인딩되는 시점에 오류가 발생하면 모델 객체에 사용자 입력 값을 유지하기 어렵다. 그래서 오류가 발생한 경우 사용자 입력 값을 보관하는 별도의 방법이 필요하다. FieldError는 오류 발생시 사용자 입력 값을 저장하는 기능을 제공함으로써 검증 오류 발생시 화면에 다시 출력할 수 있도록 한다.
위에서 rejectedValue가 오류 발생시 사용자 입력 값을 저장하는 필드이며, bindingFailure는 타입 오류 같은 바인딩이 실패했는지 여부를 적어주면 된다.
타임리프의 th:field는 정상 상황에서는 모델 객체의 값을 사용하지만, 오류가 발생하면 FieldError에서 보관한 값을 사용해서 값을 출력한다.