티스토리 뷰
초보 웹 개발자를 위한 스프링 5 프로그램 입문을 보고 복습 겸 개인 학습 정리입니다. (windows 기준)
커맨드 객체의 값 검증과 에러 메시지 처리
chap 11에서 작성한 회원 가입 예제 코드는 동작은하지만 비정상 값을 입력해도 동작하는 문제가 있다.
입력한 값에 대한 검증 처리를 하지 않는다. 또한 중복된 이메일 주소를 입력했을 때 가입 실패 이유도 알려주지 않는다.
스프링은 이 두 가지 문제를 처리하기 위해 다음 방법을 제공하고 있다.
- 커맨드 객체를 검증하고 결과를 에러 코드로 저장
- JSP에서 에러 코드로부터 메시지를 출력 ( <form:errors> 태그를 사용 )
커맨드 객체를 검증하고 결과를 에러 코드 지정하기
스프링 MVC에서 커맨드 객체 값이 올바른지 검사하려면 아래 두 인터페이스를 사용한다.
- org.springframework.validation.Validator
- org.springframewokr.validation.Errors
Validator 인터페이스를 살펴보면 supports() 메서드와 validate() 메서드로 구성되어 있다.
supports() 메서드는 파라미터로 전달받은 clazz 객체가 RegisterRequest 클래스 타입 변환이 가능한지 확인한다.
(위 코드는 supprots() 메서드를 직접 실행하지 않는다..)
validate() 메서드는 두 개의 파라미터를 갖는데, target 파라미터는 검사 대상 객체이고, errors 파라미터는 검사 결과 에러코드를 설정하기 위한 객체이다.
validate() 메서드는 다음과 같이 구현한다.
- 검사 대상 객체의 특정 프로퍼티나 상태가 올바른지 검사
- 올바르지 않다면 Errors의 rejectValue() 메서드를 이용해서 에러 코드 저장
package org.springframewokr.validation;
public interface Validator{
boolean supports(Class<?> clazz);
// supports 매서드는 Validator가 검증할 수 있는 타입인지 검사
void validate(Object target, Errors errors);
// validate 메서드는 첫 파라미터로 전달받은 객체를 검증하고 오류 결과를 Errors에 담는다
}
1. Validator 인터페이스를 구현한 클래스를 만들어보자
RegisterRequest 객체를 검증하기 위해 controller 패키지에 Validator 구현 클래스를 작성한다.
(해당 코드는 supprots() 메서드를 직접 실행하지 않는다..)
→ sp5-chap12/src/main/java/controller/RegisterRequestValidator.java
package controller;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
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);
System.out.println("RegisterRequestValidator#new(): " + this);
}
@Override // clazz객체가 RegisterRequest 클래스 타입 변환이 가능한지 확인한다
public boolean supports(Class<?> clazz) {
return RegisterRequest.class.isAssignableFrom(clazz);
}
@Override //
public void validate(Object target, Errors errors) {
System.out.println("RegisterRequestValidator#validate(): " + this);
RegisterRequest regReq = (RegisterRequest) target; // target을 실제 타입으로 변환한다
if (regReq.getEmail() == null || regReq.getEmail().trim().isEmpty()) { // email값이 유효한지 검사
errors.rejectValue("email", "required");
} else {
Matcher matcher = pattern.matcher(regReq.getEmail());
if (!matcher.matches()) {
errors.rejectValue("email", "bad");
}
}
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
ValidationUtils.rejectIfEmpty(errors, "password", "required");
ValidationUtils.rejectIfEmpty(errors, "confirmPassword", "required");
if (!regReq.getPassword().isEmpty()) {
if (!regReq.isPasswordEqualToConfirmPassword()) {
errors.rejectValue("confirmPassword", "nomatch");
}
}
}
}
public boolean supports(Class <?> clazz) { ← clazz객체가 RegisterRequest 클래스 타입 변환이 가능한지 확인한다.
return RegisterRequest.class.isAssignableFrom(clazz);
}
※ Class.isAssignableFrom()은 특정 Class가 어떤 클래스/인터페이스를 상속/구현했는지 체크한다.
public void validate(Object target, Errors errors) {
System.out.println("RegisterRequestValidator#validate(): " + this);
RegisterRequest regReq = (RegisterRequest) target; ← target을 실제 타입으로 변환한다
if (regReq.getEmail() == null || regReq.getEmail(). trim(). isEmpty()) { ← email값이 유효한지 검사
errors.rejectValue("email", "required"); ←email 값이 존재하지 않으면 에러코드로 "required"를 추가한다
} else {
Matcher matcher = pattern.matcher(regReq.getEmail()); ← email의 정규 표현식이 일치하는지 확인한다.
if (!matcher.matches()) {
errors.rejectValue("email", "bad");
}
}
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
↑
ValidationUtils 클래스를 하용하여 객체 값 검증 코드를 간결하게 작성하였다. "name"프로퍼티가 null 이거나 공백 문자일 경우 에러코드로 "required"를 추가한다.
📌 ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required"); 검사 대상 객체를 target으로 변환하지 않았는데 어떻게 "name"으로 검사할까?
→ Errors 객체
2. 커맨드 객체를 검증하도록 RegisterController 클래스를 수정
org.springframework.validation.Errors를 임포트하고 handleStep3 메서드를 수정한다.
기존 handleStep3 메서드의 커맨드 객체 파라미터(RegisterRequest regReq) 뒤에 Errors 타입 파라미터를 추가해주었다.
handleStep3 메서드를 호출할 때 커맨드 객체와 연결된 Errors 객체를 생성하여 파라미터로 전달하는데, 이 Errors객체는 특정 프로퍼티 값을 구할 수 있는 getFieldValue() 메서드를 제공한다. 따라서, ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required"); "name"에 해당하는 프로퍼티 값을 구할 수 있게 된 것.
정리하면,
errors 객체의 getFieldValue("name") 메서드를 실행 → 커맨드 객체의 특정 프로퍼티 값을 구함 → 커맨드 객체를 직접 전달하지 않아도 값을 검증할 수 있음
→ 기존 코드
변경 코드
→ sp5-chap12/src/main/java/controller/RegisterController.java
package controller;
... 생략...
import org.springframework.validation.Errors;
@Controller
public class RegisterController {
... 생략 ...
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq, Errors errors){
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";
}
}
}
코드를 자세히 살펴보면,
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq, Errors errors){ ← 호출할 때 Errors 객체를 생성해서 파라미터로 전달한다 (파라미터 위치가 반대일 경우 익셉션이 발생한다)
new RegisterRequestValidator(). validate(regReq, errors);
↑
RegisterRequestValidator를 생성하고 validate() 메서드를 실행한다.
RegisterRequest regReq 커맨드 객체 값이 올바른지 검사하고 그 결과를 Errors에 담는다
if (errors.hasErrors()) ← Errors의 hasErrors() 메서드를 이용해 에러가 존재하는지 검사한다. 유효하면 true를 리턴 하고 유효하지 않으면 Errors의 rejectValue() 메서드를 실행한다.
return "register/step2";
try { ※ regist() 메서드는 개별 이아닌 (커맨드) 객체 자체에 에러를 추가한다. (글로벌 에러라고 부름)
memberRegisterService.regist(regReq); ←동일한 이메일을 가진 회원이 있다면 예외를 발생시킨다.
return "register/step3";
}catch (DuplicateMemberException ex) { ← 위에서 발생한 익셉션 처리를 한다.
errors.rejectValue("email", "duplicate"); ← 이메일 중복에러를 추가하기 위해 "email" 프로퍼티의 에러코드 "duplicate"를 추가한다.
return "register/step2";
}
}
3. 커맨드 객체의 에러 메시지 출력하기
에러 코드를 지정하는 이유는 알맞은 에러 메시지를 출력하기 위해서다.
Errors에 에러코드를 추가하고 <form:errors> 태그를 사용하여 에러에 해당하는 메세지를 출력할 수 있다.
📌 <form:errors> 태그의 주요 속성
- element : 각 에러 메시지를 출력할 때 사용할 HTML 태그 (기본 값은 span)
- delimiter : 각 에러 메시지를 구분할 때 사용할 HTML 태그 (기본 값은 </br>)
<form:errors> 태그를 사용여 step2.jsp를 수정하자
→ chap11/src/main/webapp/WEB-INF/view/register/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="confirmPassword"/>
</label>
</p>
<input type="submit" value="<spring:message code="register.btn" />">
</form:form>
</body>
</html>
<form:errors> 태그의 path 속성은 여러 에러 메시지를 출력할 프로퍼티 이름을 지정한다.
에러 코드가 두 개 이상이 존재하면 각 에러 코드에 해당하는 메세지를 출력한다.
에러 코드에 해당하는 메시지를 찾을 때는 규칙이 있는데, 책...338페이지를 참고하자 😇
4. 에러 코드에 해당하는 메세지를 메세지 프로퍼티 파일(label.properties)에 추가하자.
→ chap11/src/main/resources/message/label.properties
member.register=회원가입
term=약관
term.agree=약관동의
next.btn=다음단계
member.info=회원정보
email=이메일
name=이름
password=비밀번호
password.confirm=비밀번호 확인
register.btn=가입 완료
register.done=<strong>{0}님</strong>, 회원가입을 완료했습니다.
go.main=메인으로 이동
// 아래 항목이 추가 된 것
required=필수항목입니다.
bad.email=이메일이 올바르지 않습니다.
duplicate.email=중복된 이메일입니다.
nomatch.confirmPassword=비밀번호와 확인이 일치하지 않습니다.
5. 서버 실행
[Run As] → [Run on Server] → [톰캣😺 서버 실행]
서버를 실행시키고 아무것도 입력하지 않고 [가입 완료] 버튼을 누르면 에러 코드에 해당하는 메세지를 출력한다.
'study > Spring' 카테고리의 다른 글
chap 13 - HttpSession (0) | 2023.02.21 |
---|---|
스프링 프로젝트 생성하기 (start.spring.io) (0) | 2023.02.16 |
chap 12 - MVC 메세지 처리 (0) | 2023.02.13 |
chap 11 - MVC 1 (2) (0) | 2023.02.10 |
chap 11 - MVC 1 (1) (0) | 2023.02.06 |
- Total
- Today
- Yesterday
- Java
- git
- 회원가입
- 한글 형태소 분석기
- Magazine K
- Django tutorial
- path variable
- 환경 변수 설정
- 혼자 공부하는 파이썬
- authenticate()
- 웹페이지
- 암호화
- API
- Python
- python3
- django-environ
- 회원 로그인
- 배열
- 검색 결과 내 페이지네이션
- git공부
- 톰캣
- django.contrib.auth
- 커맨드 객체
- django
- Spring
- error: failed to push some refs to 'https://github.com/
- 디자인 패턴
- git 공부
- 면접을 위한 CS 전공 지식 노트
- musma
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |