티스토리 뷰
이전에 구현한 회원가입 페이지는 올바르지 않은 이메일 주소를 입력하거나 이름을 입력하지 않아도 정상적으로 처리됐다.
또한 중복된 이메일 주소를 입력해서 다시 폼을 보여줄때도 에러 메시지가 출력되지 않고 그냥 다시 폼 페이지가 로드 됐다.
즉 다음 두가지가 수행되지 않았다.
1. 폼 값 검증
2. 에러 메시지 처리
스프링 MVC에서 커맨드 객체의 값이 올바른지 검사할때 다음 두가지 인터페이스를 사용한다.
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
Validator 인터페이스
public interface Validator
{
boolean supports(Class<?> clazz);
void validate(Object target, Erros errors);
}
supports() 메서드는 clazz가 Validator가 검증할수 있는 타입인지 검사한다.
validate()는 전달 받은 target을 검사하고 에러를 errors에 저장한다.
RegisterRequestValidator.java
package controller;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.ValidationUtils;
import spring.RegisterRequest;
public class RegisterRequestValidator implements Validator
{
private static final String emailRegExp =
"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" +
"[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
private Pattern pattern;
public RegisterRequestValidator()
{
pattern = Pattern.compile(emailRegExp);
}
@Override
public boolean supports(Class<?> clazz)
{
// RegisterRequest가 clazz 와 같거나 superclass 인지 확인
return RegisterRequest.class.isAssignableFrom(clazz);
}
// target: 검사 대상 객체
@Override
public void validate(Object target, Errors errors)
{
RegisterRequest regReq = (RegisterRequest)target;
// 검사 대상 객체의 특정 프로퍼티나 상태가 올바른지 검사
if(regReq.getEmail() == null || regReq.getEmail().trim().isEmpty())
{
// 올바르지 않다면 Errors의 rejectValue() 메서드를 이용해서 에러 코드 저장
// "email" 프로퍼티의 에러 코드 "required" 추가
errors.rejectValue("email", "required");
}
else
{
// 정규 표현식을 이용해 이메일이 올바른지 확인
Matcher matcher = pattern.matcher(regReq.getEmail());
if(!matcher.matches())
{ errors.rejectValue("email", "bad"); }
}
// 검사 대상 객체 (target)의 "name" 프로퍼티가 null 이거나 공백문자로만 되어 있는 경우
// "name" 프로퍼티의 에러 코드로 "required"를 추가한다
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
ValidationUtils.rejectIfEmpty(errors, "password", "required");
ValidationUtils.rejectIfEmpty(errors, "confirmPassword", "required");
if(!regReq.getPassword().isEmpty())
{
if(!regReq.isPasswordEqualToConfirmPassword())
{
errors.rejectValue("comfirmPassword", "nomatch");
}
}
}
}
RegisterRequestValidator 클래스는 RegisterRequest 객체를 검증하기 위한 클래스다.
Validator 인터페이스를 구현하므로 supports() 와 validate() 를 구현한다.
supports() 메서드
@Override
public boolean supports(Class<?> clazz)
{
// RegisterRequest가 clazz 와 같거나 superclass 인지 확인
return RegisterRequest.class.isAssignableFrom(clazz);
}
supports() 메서드는 전달받은 clazz 객체가 검증하는 객체 (커맨드 객체)와 같거나 superclass 인지 확인한다.
supports() 메서드를 직접 호출하지 않더라도, 스프링 MVC가 자동으로 검증 기능을 수행하도록 하려면 supports() 메서드가 올바르게 구현되어 있어야 한다.
validate() 메서드
// target: 검사 대상 객체
@Override
public void validate(Object target, Errors errors)
{
RegisterRequest regReq = (RegisterRequest)target;
// 검사 대상 객체의 특정 프로퍼티나 상태가 올바른지 검사
if(regReq.getEmail() == null || regReq.getEmail().trim().isEmpty())
{
// 올바르지 않다면 Errors의 rejectValue() 메서드를 이용해서 에러 코드 저장
// "email" 프로퍼티의 에러 코드 "required" 추가
errors.rejectValue("email", "required");
}
else
{
// 정규 표현식을 이용해 이메일이 올바른지 확인
Matcher matcher = pattern.matcher(regReq.getEmail());
if(!matcher.matches())
{ errors.rejectValue("email", "bad"); }
}
// 검사 대상 객체 (target)의 "name" 프로퍼티가 null 이거나 공백문자로만 되어 있는 경우
// "name" 프로퍼티의 에러 코드로 "required"를 추가한다
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
ValidationUtils.rejectIfEmpty(errors, "password", "required");
ValidationUtils.rejectIfEmpty(errors, "confirmPassword", "required");
if(!regReq.getPassword().isEmpty())
{
if(!regReq.isPasswordEqualToConfirmPassword())
{
errors.rejectValue("comfirmPassword", "nomatch");
}
}
}
validate 메서드 내부에서는 두 가지 일을 한다.
1. 검사 대상 객체의 특정 프로퍼티나 상태가 올바른지 검사하고
2. 올바르지 않다면 Errors의 rejectValue() 메서드를 이용해 에러 코드를 저장한다.
rejectValue(String field, errorcode) 는 field의 에러코드 errorcode를 추가한다.
또한 ValidationUtils 클래스를 이용해 객체의 값 검증을 한다.
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
위 코드는 검사 대상 객체 (target)의 "name" 프로퍼티가 null 이거나 공백문자로만 되어 있는 경우 "name" 프로퍼티의 에러 코드로 "required"를 추가한다
여기서 위 메서드의 파라미터로 검사 대상 객체 target 을 전달하지 않았는데 target 객체의 name 프로퍼티를 검증할수 있는 이유는 errors 객체에 있다.
아래는 RegisterController 클래스에서 handleStep3() 메서드가 커맨드 객체를 검증하도록 수정한 코드다.
RegisterController.java
package controller;
// .. 생략
@Controller
public class RegisterController
{
// .. 생략
// 회원가입 form 데이터 받아서 처리하고 step3 (회원가입 완료 페이지) 이동
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq, Errors errors)
{
// 에러 있다면 step2 로 되돌아감
new RegisterRequestValidator().validate(regReq, errors);
if(errors.hasErrors()) return "register/step2";
try
{
memberRegisterService.regist(regReq);
return "register/step3";
} catch(DuplicateMemberException ex)
{
errors.rejectValue("email", "duplicate");
return "register/step2";
}
}
}
이렇게 요청 매핑 애노테이션 적용 메서드의 커맨드 객체 파라미터 (RegisterRequest) 뒤에 Errors 타입 파라미터가 위치하면, handleStep3()가 호출될때 스프링 MVC는, 커맨드 객체와 연결된 Errors 객체를 생성해서 파라미터에 전달한다.
즉 아래처럼 handleStep3() 메서드가 정의되면
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq, Errors errors)
{}
두 번째 파라미터에 있는 Errors 객체는 스프링 MVC가 RegisterRequest 와 연결해서 전달한다는 뜻이다.
따라서 RegisterRequestValidator 클래스의 validate() 메서드에서 아래와 같이 했을때 errors 객체에서 커맨드 객체인 RegisterRequest의 프로퍼티를 get 할수 있었기 때문에 검증이 가능한 것이다.
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
글로벌 에러
커맨드 객체의 특정 프로퍼티가 아닌 커맨드 객체 자체가 잘못됐다고 표시 할 수도 있다.
여러개의 프로퍼티에 오류가 있는 경우나 예를들어 아이디와 비밀번호가 잘못됐다면 아이디, 비밀번호 각각의 프로퍼티에 에러가 있다고 표시하기 보다, 커맨드 객체 자체에 에러를 추가하는게 나을수 있다.
이런 에러를 글로벌 에러라고 부른다.
객체 자체에 에러를 추가하는 방법은 rejectValue()가 아닌 reject() 메서드로 추가한다.
에러 출력
우리의 목표는 지금까지 reject, rejectValue로 추가한 에러 메시지들을 상황에 맞게 출력되도록 하는 것이다.
이를 위해 jsp 파일을 수정해야 하는데 <form:errors> 태그를 추가해 에러 메시지를 출력할 프로퍼티를 지정할수 있다.
<form:errors path="프로퍼티 이름" />
step2.jsp 에 <form:errors> 태그를 추가한다.
step2.jsp
<%-- 입력 폼 --%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html>
<html>
<head>
<title><spring:message code="member.register"/></title>
</head>
<body>
<h2><spring:message code="member.info"/></h2>
<form:form action="step3" modelAttribute="registerRequest">
<p>
<label><spring:message code="email"/>:<br>
<form:input path="email" />
<form:errors path="email" />
</label>
</p>
<p>
<label><spring:message code="name"/>:<br>
<form:input path="name" />
<form:errors path="name" />
</label>
</p>
<p>
<label><spring:message code="password"/>:<br>
<form:password path="password" />
<form:errors path="password" />
</label>
</p>
<p>
<label><spring:message code="password.confirm"/>:<br>
<form:password path="confirmPassword" />
<form:errors path="comfirmPassword" />
</label>
</p>
<input type="submit" value="<spring:message code="register.btn"/>">
</form:form>
</body>
</html>
이렇게 하면 해당 프로퍼티에 추가된 에러가 있다면, 해당 에러코드에 해당하는 메시지를 출력한다.
메시지의 출력은 MessageSource를 사용한다. (https://tose33.tistory.com/799?category=1065882)
MessageSource로 지정한 label.properites 에 다음을 추가한다.
label.properties
// ... 생략
required=필수항목입니다
bad.email=이메일이 올바르지 않습니다
duplicate.email=중복된 이메일입니다
nomatch.confirmPassword=비밀번호와 확인이 일치하지 않습니다
<form:errors path="프로퍼티 이름"> 태그가 프로퍼티 이름에 추가된 에러코드의 메시지를 찾을때 label.properties에서 찾을텐데 찾는 순서는 다음과 같다.
1. 에러코드 + "." + 커맨드객체이름 + "." + 필드명
2. 에러코드 + "." + 필드명
3. 에러코드 + "." + 필드타입
4. 에러코드
이 순서중 먼저 찾아지는 메시지를 출력한다.
예를들어 아래와 같이 errors.rejectValue("email", "required")로 email 프로퍼티에 required 에러 코드를 추가했다면

1. required.registerRequest.email
2. required.email
3. required.String
4. required
순으로 찾게되고 label.jsp에서 네번째인 required를 찾아 출력한다.
출처 : 스프링5 프로그래밍 입문 (최범균 저)
'Web' 카테고리의 다른 글
| Ch12. MVC 2 : Bean Validation을 이용한 검증 (0) | 2022.07.22 |
|---|---|
| Ch12. MVC 2 : Validator의 범위 (0) | 2022.07.22 |
| Ch12. MVC 2 : 메시지 (0) | 2022.07.19 |
| Ch11. MVC 1 : ModelAndView (0) | 2022.07.18 |
| Ch11. MVC 1 :Model을 통한 컨트롤러에서 뷰에 데이터 전달 , model.addAttribute() (0) | 2022.07.18 |
- Total
- Today
- Yesterday
- C
- Brute Force
- MVC
- C++
- Stack
- dfs
- db
- priority queue
- CSS
- greedy
- Spring
- 이분탐색
- Implementation
- back tracking
- graph
- BFS
- recursion
- binary search
- two pointer
- permutation
- Python
- Tree
- 자료구조
- 조합
- Dijkstra
- Unity
- floyd warshall
- DP
- 재귀
- Kruskal
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
