본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] Validator로 form 데이터 검증하기

by 김코더 김주역 2021. 1. 6.
반응형

폼 화면에서 사용자가 실수로 textfield에 아무것도 입력하지 않았거나 제한 글자수를 초과했을 경우에는 사용자에게 다시 입력하게 할 수 있다. 이 과정에서 사용자가 적절한 요청 필드값을 전달했는지에 관한 검증이 필요하다.

자바 스크립트의 onclick을 이용해도 되지만, 이번 포스팅에서는 Spring이 제공하는 Validator을 이용하여 컨트롤러에서 요청 데이터를 검증하는 방법을 알아볼 것이다.

 

이번 포스팅에서 사용할 Validator, BindingResult, Errors에 대해 간단하게 알아보자.

  • Validator : Spring에서 범용적으로 사용할 수 있는 오브젝트 검증기로, bean으로 등록해서 사용 가능함
  • BindingResult : 바인딩 작업의 결과와 Validator를 통한 검증의 결과가 담겨있는 객체로, ModelAndView의 모델 맵에 자동으로 추가됨
  • Errors : 에러 정보를 등록할 때 사용되는 객체

 

Validator, BindingResult, Errors에 대한 공식 문서 링크를 첨부했으니 같이 보면서 이해하면 좋을 것 같다.

 

 Validator의 메소드를 직접 호출하는 방법과 Spring에서 호출하는 방법 2가지를 예제를 통해 소개할 것이다.

 

예제 프로젝트의 구성은 이렇다.

 

 

1. 직접 호출

<HomeController.java>

check 메소드에서는 전송 받은 form 데이터들을 user 객체에 저장해두고, UserValidator 클래스의 validate 메소드를 호출하여 form 데이터를 검증하고, 에러가 하나 이상 있다면 중간 경로 "fail"을 리턴해줘서 fail.jsp로 이동시켜준다.

검증 과정에서 발견한 에러 정보는 BindingResult 객체에 담기게 되며, 에러가 하나 이상이라면 hasErrors 메소드는 true를 반환해주는 것이다.

컨트롤러 메소드의 BindingResult 파라미터의 바로 앞에는 반드시 검증 대상 객체가 와야 한다.

만약 에러가 없어서 if문에 걸리지 않는다면 그대로 중간 경로 "success" 를 리턴해줘서 success.jsp로 이동시켜준다.

package com.example.demo;

import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {			
		return "home";
	}
	
	@RequestMapping("/check")
	public String check(User user, BindingResult result) {
		String page = "success";
		UserValidator userValidator = new UserValidator();
		userValidator.validate(user, result);
		if(result.hasErrors()) {
			page = "fail";
		}
		return page;
	}
}

 

 

<User.java>

package com.example.demo;

public class User {
	private String id;
	private String pw;
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPw() {
		return pw;
	}
	public void setPw(String pw) {
		this.pw = pw;
	}
}

 

 

<UserValidator.java>

Validator 인터페이스를 가져오면 supports() 메소드와 validate() 메소드를 override해야 한다.

supports() 메소드는 검증할 수 있는 오브젝트 타입인지의 여부를 반환해야 하며, supports() 메소드가 true를 반환해야 validate() 메소드가 호출된다.

validate() 메소드는 프로퍼티 바인딩이 완료된 오브젝트에 대해서 검증을 진행하는 메소드로, 아래 예시에서는 User 객체의 id, pw를 검사해서 null 또는 공백이면 Errors에 오류 정보를 등록한다.

package com.example.demo;

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

public class UserValidator implements Validator{

	@Override
	public boolean supports(Class<?> clazz) {
		// TODO Auto-generated method stub
		return User.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		User user = (User) target;
		String userId = user.getId();
		String userPw = user.getPw();
		if(userId == null || userId.trim().isEmpty()) {
			errors.rejectValue("id", "null or empty id value"); // 문제가 있는 프로퍼티와 에러 메시지를 함께 등록
		}
		if(userPw == null || userPw.trim().isEmpty()) {
			errors.rejectValue("pw", "null or empty pw value");
		}
	}

}

참고로, ValidationUtils의 rejectIfEmptyOrWhitespace() 메소드를 사용하면 if 문을 작성할 필요 없이 필드가 null 또는 공백인 경우에 에러를 추가해줄 수도 있다.

ValidationUtils의 더 많은 메소드들은 아래 공식 문서에서 확인할 수 있다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/ValidationUtils.html

 

ValidationUtils (Spring Framework 5.3.23 API)

Reject the given field with the given error code, error arguments and default message if the value is empty or just contains whitespace. An 'empty' value in this context means either null, the empty string "", or consisting wholly of whitespace. The object

docs.spring.io

그리고 에러가 있는 필드를 굳이 명시하고 싶지 않다면 rejectValue() 메소드 대신에 reject() 메소드를 사용해서 에러 정보를 추가하면 된다.

 

 

<home.jsp>

ID, PW를 입력 하고 제출을 누른다.

<%@ page language="java" contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
<h1>
	Sign up
</h1>
<form action="/demo/check">
	ID : <input type="text" name="id"><br>
	PW : <input type="password" name="pw"><br>
	<input type="submit">
</form>
</body>
</html>

 

 

<success.jsp>

성공 화면, 사용자가 입력한 ID, PW를 출력한다.

<%@ 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>
	<h1>SUCCESS!</h1>
	ID : ${user.id} <br>
	PW : ${user.pw}
</body>
</html>

 

 

<fail.jsp>

실패 화면, 알림창을 띄우고 사용자가 확인을 누르면 다시 Controller을 통해 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>
	<h1>FAIL!</h1>
	<script>
		alert("No Input In Some Textfield!");
		window.location.replace("http://localhost:8181/demo");
	</script>
</body>
</html>

 

 

실행 화면

<home.jsp>

 

<success.jsp>

 

<fail.jsp>

 

 

 

 

2. Spring 호출

hibernate validator 라이브러리를 이용하기 위해 pom.xml에 다음과 같이 dependency를 추가해주자.

<dependencies>
  ...
  <dependency>
    <groupId> org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.2.0.Final</version>
  </dependency>
</dependencies>

 

 

<HomeController.java>

바인더 메소드를 하나 만들고 @InitBinder 어노테이션을 이 메소드 위에 작성해준다.

아래 예시에서는 initbinder 메소드가 바인더 메소드이며, WebDataBinder객체에 UserValidator클래스를 validator로 사용할 것이라고 명시해주고 있다.

직접 호출 예시에서 작성했던 호출 부분은 이제 필요가 없다. 대신, 파라미터에서 form 정보가 담긴 데이터 객체에 @Valid 어노테이션을 붙여줘야 정상적으로 검증이 이루어진다.

package com.example.demo;

import java.util.Locale;

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {			
		return "home";
	}
	
	@RequestMapping("/check")
	public String check(@Valid User user, BindingResult result) {
		String page = "success";
		if(result.hasErrors()) {
			page = "fail";
		}
		return page;
	}
	
	@InitBinder
	protected void initBinder(WebDataBinder binder) {
		binder.setValidator(new UserValidator());
	}
}

 

 

+ 보충) 다른 계층에서 Validator 사용하기

- Validator 오브젝트를 bean으로 등록해서 다른 계층에서 사용할 수도 있다. 그러나 Validator.validate() 메소드를 직접 호출하기 위해 BindingResult 타입의 오브젝트를 직접 만들어야 한다는 것이 번거로울 수도 있다. 이 경우에는 BindingResult의 구현체인 BeanPropertyBindingResult의 사용을 고려해보자.

 

반응형

댓글