[Spring Boot] 06. 유효성 검증(Validator)

하림·2024년 8월 28일

Spring

목록 보기
2/16
post-thumbnail

1. Validator 인터페이스를 통한 유효성 검증

1.1 소스코드 작성하기

홈을 생성합니다. 다음과 같이 webapp/WEB-INF/views/home.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>
	<h2>스프링 부트 프로젝트</h2>
	<ul>
		<li><a href="/">루트</a></li>
	</ul>
	
	<h2>Validator 인터페이스</h2>
	<ul>
		<li><a href="./write.do">글쓰기폼</a>
	</ul>
</body>
</html>

컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 를 작성합니다.

package com.edu.springboot;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MainController {
	@RequestMapping("/")
	public String home() {
		return "home";
	}
	
	@RequestMapping("/write.do")
	public String insert1() {
		return "write";
	}
}

뷰를 생성합니다. 다음과 같이 webapp/WEB-INF/views/write.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>
	<script>
	// 매개변수 f는 <form> 태그의 DOM을 전달받는다.
	function sending(f, gubun) {
		// gubun에 의해 다른 action 속성을 부여한다.
		if(gubun == 1) 
			f.action="./writeAction1.do"
		else
			f.action="./writeAction2.do"
		// 여기에서 폼값을 전송한다.
		f.submit();
	}
	</script>
	
	<h2>Validator 인터페이스를 통한 유효성 검증</h2>
	<form method="post">
		일련변호 : <input type="number" name="idx" value="1">
		<br/>
		아이디 : <input type="text" name="userid" value="${ dto.userId }">
		<br/>
		제목 : <input type="text" name="title" value="${ dto.title }">
		<br/>
		내용 : <input type="text" name="content" value="${ dto.content }">
		<br/>
		<input type="button" value="전송1" onclick="sending(this.form, 1);">
		<input type="button" value="전송2" onclick="sending(this.form, 2);">
	</form>
</body>
</html>

DTO를 생성합니다. 다음과 같이 com.edu.springboot.BoardDTO.java 를 작성합니다.

package com.edu.springboot;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/* 롬복을 통해 멤버변수에 대한 getter, setter, 기본생성자 등을 일괄적으로 정의할 수 있다.
 * 또한 Object 클래스에서 제공되는 toString()과 같은 메서드도 자동으로 오버라이딩 된다. */
@Data
// 인수를 가진 생성자를 추가한다.
@AllArgsConstructor
// 기본 생성자를 추가한다.
@NoArgsConstructor
public class BoardDTO {
	private int idx;
	private String userid;
	private String title;
	private String content;
}

검증 클래스를 생성합니다. 다음과 같이 com.edu.springboot.BoardValidator.java 를 작성합니다.

package com.edu.springboot;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/* 폼값 검증을 위한 클래스 제작을 위해 먼저 Validator 인터페이스를 구현한다.
 * 그리고 추상메서드 2개를 필수로 오버라이딩 한다. */
public class BoardValidator implements Validator {
	
	/* 검증을 위한 커맨드 객체의 클래스 타입을 확인하는 용도의 메서드 */
	@Override
	public boolean supports(Class<?> clazz) {
		System.out.println("supports() 호출됨");
		// 폼값을 받기 위한 커맨드 객체의 타입만 명시해주면 된다.
		return BoardDTO.class.isAssignableFrom(clazz);
	}
	
	/* 폼값 검증을 하기 위해 3가지 방법을 사용할 수 있다.
	 * 1. if문을 이용한 검증
	 * 2. ValidationUtils 클래스를 이용한 검증
	 * 3. 사용자 정의 메서드를 이용한 검증 */
	@Override
	public void validate(Object target, Errors errors) {
		System.out.println("validate() 호출됨");
		
		
		// 폼값 전체를 저장한 DTO 객체는 Object 타입으로 전달되므로 형변환된다.
		BoardDTO boardDTO = (BoardDTO) target;
		
		// 폼값을 저장하고 있는 커맨드 객체인지 확인 (필요한 경우에 사용)
		if (supports(boardDTO.getClass()) == true) {
			System.out.println("폼값 검증에 적합한 인스턴스");
		}
		else {
			System.out.println("폼값 검증에 적합한 인스턴스가 아님");
		}
		
		// 1. 아이디 검증
		String userid = boardDTO.getUserid();
		// 만약 아이디가 null 이거나 빈값이라면 아래 내용을 실행한다.
		if (userid == null || userid.trim().isEmpty()) {
			// 개발자가 콘솔에 출력하는 검증 내용
			System.out.println("아이디를 입력하세요.");
			/* 폼값 검증 시 오류가 있는 경우 처리 형식
			 * Errors인스턴스.rejectValue(폼의name속성, 여러객체명, 디폴트메세지); */
			errors.rejectValue("userid", "idError", "아이디 검증 실패");
		}
		
		/* 2. 제목 검증
		 * : ValidationUtils 클래스의 정적메서드를 호출하여 빈 값이거나 null값인 경우를 검증한다. */
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "titleError", "제목 검증 실패");
		
		/* 3. 내용 검증 : 개발자가 정의한 메서드를 통해 검증한다. */
		boolean contentValidate = myEmptyOrWhitespace(boardDTO.getContent());
		if (contentValidate == false) {
			System.out.println("내용을 입력해주세요.");
			errors.rejectValue("content", "contentError", "내용 검증 실패");
		}
	}
	
	public boolean myEmptyOrWhitespace(String value) {
		// 내용이 null이거나 길이가 0인 경우 false를 반환한다.
		if (value == null || value.trim().length() == 0) {
			return false;
		}
		else {
			return true;
		}
	}
}

컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 에 추가적인 코드를 작성합니다.

package com.edu.springboot;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MainController {
	@RequestMapping("/")
	public String home() {
		return "home";
	}
	
	@RequestMapping("/write.do")
	public String insert1() {
		return "write";
	}
	
	/* Validator 인터페이스를 통한 폼값의 유효성 검증
	 * Spring에서 사용하는 커맨드 객체는 전송된 폼값을 한꺼번에 받아 Model 인스턴스에 저장해준다.
	 * 만약 저장되는 속성명을 변경하고 싶다면 @ModelAttribute 어노테이션을 사용하면 된다.
	 * 아래의 경우 boardDTO가 아닌 dto라는 이름으로 Model에 저장된다. */
	@RequestMapping("/writeAction1.do")
	public String writeAction1(@ModelAttribute("dto") BoardDTO boardDTO, BindingResult result) {
		// 폼값 검증에 성공한 경우 포워드 할 View의 경로 설정
		String page = "result";
		System.out.println(boardDTO);
		
		// 폼값 검증을 위한 인스턴스 생성
		BoardValidator validator = new BoardValidator();
		/* 폼값을 저장한 DTO 및 검증결과 전달을 위한 객체를 인수로 전달한다.
		 * 여기서 validate()를 호출하여 검증을 진행하게 된다. */
		validator.validate(boardDTO, result);
		
		// 폼값 검증에 실패한 경우
		if (result.hasErrors()) {
			// 실패한 경우 재입력을 받기 위해 쓰기페이지로 포워드
			page = "write";
			/* result가 폼값 검증에 대한 모든 결과값을 가지고 있다.
			 * 따라서 아래 부분의 출력문을 주석처리 하더라도 이 부분에서 모든 검증 결과가 출력된다. */
			System.out.println("검증 실패 반환값 1 : " + result.toString());
			
			// 제목 검증에 실패한 경우 우리가 생성한 에러코드를 출력한다.
			if (result.getFieldError("title") != null) {
				System.out.println("제목 검증 1 (에러코드)  : " + result.getFieldError("title").getCode());
			}
			// 내용 검증에 실패한 경우 디폹트 메세지를 출력한다.
			if (result.getFieldError("content") != null) {
				System.out.println("내용 검증 1 (디폴트 메세지)  : " + result.getFieldError("content").getDefaultMessage());
			}	
		}
		return page;
	}
}

뷰를 생성합니다. 다음과 같이 webapp/WEB-INF/views/result.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>
	<h2>글쓰기 결과</h2>
	<p>
		일련번호 : ${dto.idx} <br />
		아이디 : ${dto.userid} <br />
		제목 : ${dto.title} <br />
		내용 : ${dto.content}	
	</p>
</body>
</html>

1.2 실행하기

다음과 같이 실행됩니다.

'글쓰기폼' 링크를 클릭하면 다음과 같이 글을 작성할 수 있습니다.

아무것도 입력하지 않고 '전송1' 버튼을 누르면 콘솔에 다음과 같이 출력됩니다.

BoardDTO(idx=1, userid=, title=, content=)
validate() 호출됨
supports() 호출됨
폼값 검증에 적합한 인스턴스
아이디를 입력하세요.
내용을 입력해주세요.
검증 실패 반환값 1 : org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'dto' on field 'userid': rejected value []; codes [idError.dto.userid,idError.userid,idError.java.lang.String,idError]; arguments []; default message [아이디 검증 실패]
Field error in object 'dto' on field 'title': rejected value []; codes [titleError.dto.title,titleError.title,titleError.java.lang.String,titleError]; arguments []; default message [제목 검증 실패]
Field error in object 'dto' on field 'content': rejected value []; codes [contentError.dto.content,contentError.content,contentError.java.lang.String,contentError]; arguments []; default message [내용 검증 실패]
제목 검증 1 (에러코드)  : titleError
내용 검증 1 (디폴트 메세지)  : 내용 검증 실패

이번에는 내용을 입력한 후에 '전송1' 버튼을 누릅니다.

폼에 입력한 값이 전송된 페이지가 나타납니다.

콘솔에는 다음과 같이 출력됩니다.

BoardDTO(idx=1, userid=harim, title=제목, content=제목입니다)
validate() 호출됨
supports() 호출됨
폼값 검증에 적합한 인스턴스




2. Vaildation 어노테이션을 통한 유효성 검증

2.1 소스코드 작성하기

다음과 같이 build.gradle 파일에 Validation 어노테이션을 위한 위존을 설정합니다.

// validation 어노테이션을 위한 의존설정
implementation 'org.springframework.boot:spring-boot-starter-validation'

VO를 생성합니다. 다음과 같이 com.edu.springboot.BoardVO.java 를 작성합니다.

package com.edu.springboot;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;

/* 어노테이션을 통한 폼값 검증을 위해 디펜던시를 추가해야 한다.
 * build.gradle에 starter-validation 항목을 추가한 후 refresh 한다. */
@Data
public class BoardVO {
	
	private int idx;
	
	/* @NotNull : 폼값이 null일 때 메세지를 출력한다.
	 * @NotEmpty : 빈값일 때 메세지를 출력한다.
	 * @Size : 입력값의 길이를 지정한다. 해당 범위를 벗어났을 때 메세지를 출력한다. */
	@NotNull(message = "아이디가 null입니다.")
	@NotEmpty(message = "아이디를 입력해주세요.")
	@Size(min = 5, max = 12, message = "아이디는 5~12자로 입력해주세요.")
	private String userid;
	
	@NotNull(message = "제목이 null입니다.")
	@NotEmpty(message = "제목을 입력해주세요.")
	private String title;
	
	@NotNull(message = "내용이 null입니다.")
	@NotEmpty(message = "내용을 입력해주세요.")
	private String content;
}

컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 를 작성합니다.

/* 어노테이션을 통한 검증이므로 폼값 저장을 위한 VO객체에 @Validated를 추가한다. */
@RequestMapping("/writeAction2.do")
public String writeAction2(@ModelAttribute("dto") @Validated BoardVO boardVO, BindingResult result) {
	String page = "result";
	System.out.println(boardVO);
		
	/* 검증을 위한 클래스는 별도로 정의할 필요가 없으므로 주석 처리한다. */
	// BoardValidator validator = new BoardValidator();
	// validator.validate(boardVO, result);
		
	if (result.hasErrors()) {
		page = "write";
		System.out.println("검증 실패 반환값 1 : " + result.toString());
			
		if (result.getFieldError("title") != null) {
			System.out.println("제목 검증 1 (에러코드)  : " + result.getFieldError("title").getCode());
		}
		if (result.getFieldError("content") != null) {
			System.out.println("내용 검증 1 (디폴트 메세지)  : " + result.getFieldError("content").getDefaultMessage());
		}	
	}
	return page;
}

1.2 실행하기

다음과 같이 실행됩니다.

폼에 아무것도 입력하지 않고 '전송2' 버튼을 누르면 콘솔에 다음과 같이 출력됩니다.

BoardVO(idx=1, userid=, title=, content=)
검증 실패 반환값 1 : org.springframework.validation.BeanPropertyBindingResult: 4 errors
Field error in object 'dto' on field 'userid': rejected value []; codes [NotEmpty.dto.userid,NotEmpty.userid,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dto.userid,userid]; arguments []; default message [userid]]; default message [아이디를 입력해주세요.]
Field error in object 'dto' on field 'userid': rejected value []; codes [Size.dto.userid,Size.userid,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dto.userid,userid]; arguments []; default message [userid],12,5]; default message [아이디는 5~12자로 입력해주세요.]
Field error in object 'dto' on field 'content': rejected value []; codes [NotEmpty.dto.content,NotEmpty.content,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dto.content,content]; arguments []; default message [content]]; default message [내용을 입력해주세요.]
Field error in object 'dto' on field 'title': rejected value []; codes [NotEmpty.dto.title,NotEmpty.title,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dto.title,title]; arguments []; default message [title]]; default message [제목을 입력해주세요.]
제목 검증 1 (에러코드)  : NotEmpty
내용 검증 1 (디폴트 메세지)  : 내용을 입력해주세요.

모든 내용을 입력한 후에 '전송2'를 누르면 콘솔에 다음과 같이 출력됩니다.

BoardVO(idx=1, userid=harim, title=제목, content=내용입니다)

0개의 댓글