본문 바로가기

Spring

Spring - 유효성 검사(Validation)

유효성을 검사하는 것은 어떤 데이터의 값이 유효한지, 타당한 것인지 확인하는 것이다.

간단한 예를 들면 어떤 페이지의 회원가입을 하다보면 비밀번호를 8자리이상 입력하라거나 이메일 양식이 일치하지 않는다는 메세지등이 곧 유효성 검사를 하는 과정에서 나온것이다.

 

- 웹 브라우저 : 자바스크립트로 웹서버에 전송하기 전 검사한다.

 

- 웹 서버: 전달 받은 요청 파라미터를 검사한다.

 

자바스크립트에서 유효성 검사를 하지만 서버에서도 유효성 검사를 반드시 해줘야 한다. 

 

 

> sample.xml

 <bean id="memService" class="member.MemberService"/>
   
   <bean class="member.RegistrationController">
    <property name="memberService" ref="memService"></property>
   </bean>
   
   <bean id="messageSource"
   		class="org.springframework.context.support.ResourceBundleMessageSource">
   		<property name="basenames">
   			<list>
   				<value>message.error</value>
   			</list>
   		</property>
   		<property name="defaultEncoding" value="UTF-8"/>
   </bean>

Spring에서 Message Properties를 사용하기 위한 방법은 2가지가 있는데, ResourceBundleMessageSource 와 ReloadableResourceBundleMessageSource 클래스다. (ReloadableResourceBundleMessageSource 클래스는 해당 Properties 파일이 변경되었을 경우에도 애플리케이션을 다시 시작할 필요하 없다는 장점이 있음)

하지만 이번 예제에서는 ResourceBundleMessageSource를 defaultEncoding통해 UTF-8로 설정하여 사용해보겠다.

 

 

> error.properties

required=필수 항목입니다.
required.email=이메일을 입력하세요.
minlength=최소 {1} 글자 이상 입력해야 합니다.
maxlength=최대 {1} 글자까지만 입력해야 합니다.
invalidIdOrPassword.loginCommand=아이디와 암호가 일치하지 않습니다.
invalidPassword=암호가 일치하지 않습니다.
shortPassword=암호 길이가 짧습니다.
notSame.confirmPassword=입력한 값이 암호와 같지 않습니다.
typeMismatch.birthday=날짜 형식이 올바르지 않습니다.

NotEmpty=필수 항목입니다.
NotEmpty.currentPassword=현재 암호를 입력하세요.
Email=올바른 이메일 주소를 입력해야 합니다.

badBeginDate=이벤트 시작일이 잘못되었습니다.
badEndDate=이벤트 종료일이 잘못되었습니다.
typeMismatch.beginDate=시작 날짜 형식이 올바르지 않습니다.
typeMismatch.endDate=종료 날짜 형식이 올바르지 않습니다.

실제 properties파일을 들어가보면 글자가 깨져있다. 메모장을 UTF-8로 저장하여 확인할 수 있다.

 

 

MemberRegistValidator.java

public class MemberRegistValidator implements Validator	{

	@Override
	public boolean supports(Class<?> clazz) {
		return MemberRegistRequest.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		MemberRegistRequest regReq = (MemberRegistRequest) target;
		if(regReq.getEmail()==null || regReq.getEmail().trim().isEmpty()) {
			errors.rejectValue("email", "required");
		}
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmPassword", "required");
		if(regReq.hasPassword()) {
			if(regReq.getPassword().length() < 5) {
				errors.rejectValue("password", "shortPassword");
			}
			else if(!regReq.isSamePasswordConfirmPassword()) {
				errors.rejectValue("confirmPassword", "notSame");
			}
		}
		Address address = regReq.getAddress();
		if(address==null) {
			errors.rejectValue("address", "required");
		}
		else {
			errors.pushNestedPath("address");
			try {
				ValidationUtils.rejectIfEmptyOrWhitespace(errors, "address1", "required");
				ValidationUtils.rejectIfEmptyOrWhitespace(errors, "address2", "required");
			} finally {
				errors.popNestedPath();
			}
		}
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "birthday", "required");
	}

}

Validator 인터페이스를 상속한 클래스는 두 개의 메서드를 구현해야 한다.

1. boolean supports(Class clazz) : 어떤 타입의 객체를 검증할 때 이 객체의 클래스가 Validator가 검증할 수 있는 클래스인 지를 판단하는 메서드

2. void validate(Object target, Errors error) : 실제 검증 로직이 이루어지는 메서드, 구현할 때 ValidationUtils를 사용하여 편리하게 작성 가능

 

supports 메서드에서 인자로넘어온 객체의 클래스가 지정한 형식의 인스턴스를 현재 형식의 변수에 할당할 수 있는지 여부를 결정. 

validate 메서드에서는 password의 길이가 5자리 이상인지 검증하는 등 여러가지 조건문을 걸어주며  rejectValue()로 필드(에 대한 에러정보 추가(에러코드 및 메시지, 메시지 인자 전달), ValidationUtilsrejectIfEmptyOrWhitespace() 를 사용하여 객체의 각 필드가 비어있거나 공백일 경우에는 errors에 에러 정보를 담는 로직을 구현하였다. 각각 error.properties에 들어있는 키 값을 넣어줬다.

 

 

registrationForm.jsp 의 일부분

<spring:hasBindErrors name="memberInfo"/>
<form method="post">
<label for="password">암호</label>:
<input type="password" name="password" id="password" value="${memberInfo.password}"/>
<form:errors path="memberInfo.password"/><br/>
<label for="password">암호확인</label>:
<input type="password" name="confirmPassword" id="confirmPassword" value="${memberInfo.confirmPassword}"/>
<form:errors path="memberInfo.confirmPassword"/><br/>
</form>

<spring:hasBindErrors> name 속성에 커맨드객체명을 넣고,

<form:errors> path 속성에 에러코드(커맨드객체명.이름)를 입력한다.

 

 

 

샘플로 가입시 유효성검사를 통해 조건으로 준 에러메세지들이 옆에 나오도록 jsp파일을 만들어 실행해보았다.

에러메세지들이 잘 나오는 것을 확인할 수 있다. 

 

 

 

 

 그 외 클래스 및 메서드들 

더보기

MemberInfo.java

public class MemberInfo {
	private String id;
	private String name;
	private String email;
	private String password;
	private boolean allowNoti;
	private Address address;
	
	public MemberInfo(String id, String name, String email, String password, boolean allowNoti, Address address) {
		this.id = id;
		this.name = name;
		this.email = email;
		this.password = password;
		this.allowNoti = allowNoti;
		this.address = address;
		
	}
	
	public boolean matchPassword(String inputPassword) {
		return password.equals(inputPassword);
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public boolean isAllowNoti() {
		return allowNoti;
	}

	public void setAllowNoti(boolean allowNoti) {
		this.allowNoti = allowNoti;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}
	
	
}

Address.java

public class Address {
	private String address1;
	private String address2;
	private String zipcode;
	
	public String getAddress1() {
		return address1;
	}
	public void setAddress1(String address1) {
		this.address1 = address1;
	}
	public String getAddress2() {
		return address2;
	}
	public void setAddress2(String address2) {
		this.address2 = address2;
	}
	public String getZipcode() {
		return zipcode;
	}
	public void setZipcode(String zipcode) {
		this.zipcode = zipcode;
	}
}


MemberRegistRequest.java

public class MemberRegistRequest {
	private String email;
	private String name;
	private String password;
	private String confirmPassword;
	private boolean allowNoti;
	private Address address;
	private Date birthday;
	
	public boolean isSamePasswordConfirmPassword() {
		if(password == null || confirmPassword == null) {
			return false;
		}
		return password.equals(confirmPassword);
	}
	
	public boolean hasPassword() {
		return password != null && password.trim().length() > 0;
	}
	
	@DateTimeFormat(pattern="yyyyMMdd")
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getConfirmPassword() {
		return confirmPassword;
	}

	public void setConfirmPassword(String confirmPassword) {
		this.confirmPassword = confirmPassword;
	}

	public boolean isAllowNoti() {
		return allowNoti;
	}

	public void setAllowNoti(boolean allowNoti) {
		this.allowNoti = allowNoti;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	public Date getBirthday() {
		return birthday;
	}
	
	
}

 

 

 

 

> RegistrationController.java

@Controller
@RequestMapping("/member/regist")
public class RegistrationController {
	private static final String MEMBER_REGISTRATION_FROM = "member/registrationForm";
	
	private MemberService memberService;
	
	@RequestMapping(method = RequestMethod.GET)
	public String form(@ModelAttribute("memberInfo") MemberRegistRequest memRegReq) {
		return MEMBER_REGISTRATION_FROM;
	}
	
	@RequestMapping(method = RequestMethod.POST)
	public String regist(@ModelAttribute("memberInfo") MemberRegistRequest memRegReq, BindingResult bindingResult) {
		new MemberRegistValidator().validate(memRegReq, bindingResult);
		if(bindingResult.hasErrors()) {
			return MEMBER_REGISTRATION_FROM;
		}
		memberService.registNewMember(memRegReq);
		return "member/registered";
	}
	
	public void setMemberService(MemberService memberService) {
		this.memberService = memberService;
	}
	
}

 

BindingResult의 hasErrors() 메서드는 검증 결과 오류가 있다면 true를 리턴한다!

 

> MemberService.java

public class MemberService {
	private int nextMemberId = 0;
	private Map<String, MemberInfo> memberMap = new HashMap<>();
	
	public MemberService() {
		memberMap.put("m1", new MemberInfo("m1","이상화","sanghwa@sanghwa.com","sanghwa",
				false,new Address()));
		memberMap.put("m2", new MemberInfo("m2","김연아","yuna@yuna.com","yuna",
				false,new Address()));
		nextMemberId = 3;
	}
	
	public void registNewMember(MemberRegistRequest memRegReq) {
		MemberInfo mi = new MemberInfo("m"+nextMemberId,
				memRegReq.getName(), memRegReq.getEmail(), memRegReq.getPassword(),
				 memRegReq.isAllowNoti(), memRegReq.getAddress());
		memberMap.put(mi.getId(),mi);
	}
}

 

registrationForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html><html><head><title>Insert title here</title></head><body>
<spring:hasBindErrors name="memberInfo"/>
<form method="post">
<label for="email">이메일</label>:
<input type="text" name="email" id="email" value="${memberInfo.email}"/>
<form:errors path="memberInfo.email"/><br/>
<label for="name">이름</label>:
<input type="text" name="name" id="name" value="${memberInfo.name}"/>
<form:errors path="memberInfo.name"/><br/>
<label for="password">암호</label>:
<input type="password" name="password" id="password" value="${memberInfo.password}"/>
<form:errors path="memberInfo.password"/><br/>
<label for="password">암호확인</label>:
<input type="password" name="confirmPassword" id="confirmPassword" value="${memberInfo.confirmPassword}"/>
<form:errors path="memberInfo.confirmPassword"/><br/>
<label>주소</label>:
주소1
<input type="text" name="address.address1" value="${memberInfo.address.address1}"/>
<form:errors path="memberInfo.address.address1"/><br/>
주소2
<input type="text" name="address.address2" value="${memberInfo.address.address2}"/>
<form:errors path="memberInfo.address.address2"/><br/>
우편번호
<input type="text" name="address.zipcode" value="${memberInfo.address.zipcode}"/>
<form:errors path="memberInfo.address.zipcode"/><br/>
<label>
<input type="checkbox" name="allowNoti" value="true" ${memberInfo.allowNoti ? 'checked' : '' }/>
이메일을 수신합니다.
</label><br/>
<label for="birthday">생일</label> - 형식 : YYYYMMDD, ex) 19950731
<input type="text" name="birthday" id="birthday" 
	value='<fmt:formatDate value="${memberInfo.birthday}" pattern="yyyyMMdd" /> '/>
<form:errors path="memberInfo.birthday"/><br/>
<input type="submit" value="가입하기" />
</form></body></html>

 

registrationForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
${memberInfo.name} 님이 회원가입을 완료했습니다.
</body>
</html>

 

 

 

 

 

 

728x90
반응형