<< 학습 목표 >>

1. InitBinder 를 사용해 Validator 를 생성할 수 있다.

2. InitBinder 가 생성한 Validator 로 커맨드 객체를 검증할 수 있다.

3. 애너테이션을 사용해 커맨드 객체 검증 코드를 최대한 줄일 수 있다.


앞서 우리는 Validator 를 컨트롤러 안에서 직접 생성했음

 

type1 컨트롤러는 커맨드 객체를 검증하기 위해 Validator 가 필요한데 이를 "type1 컨트롤러는 Validator에 의존(Dependency)한다" 라고 표현함

type1 컨트롤러와 Validator 사이에서 Validator 는 의존 객체라고 부름

 

A가 B에 의존할 때 A가 B를 직접 생성하면 이를 강한 결합이라 부르고 이는 좋지 못한 코드가 됨

왜 그런지는 Chapter03 에서 배울 것

A와 B 사이에서 B는 의존 객체라고 부름

 

A가 B에 의존할 때 A 안에서 B를 직접 생성하는 방식이 아닌 다른 객체의 도움으로 A가 B를 생성하도록 해야하는데 이를 Spring Framework가 담당함

 

type1 컨트롤러가 Validator에 의존하고 있는데 type1 컨트롤러 안에서 Validator를 직접 생성하는게 아니라 Spring Framework의 도움으로 type1 컨트롤러가 Validator 를 생성하도록 해야함

이렇게  A가 누군가의 도움으로 B(의존 객체)를 생성하면 이를 약한 결합이라 부르고 이는 좋은 코드가 됨

왜 그런지는 Chapter03에서 배울 것


컨트롤러가 Spring Framework의 도움으로 Validator를 생성하려면 Spring Framework가 제공하는 initBinder를 사용해야함

따라서 구체적으로 말하면 컨트롤라가 initBinder의 도움으로 Validator를 생성하도록 해보자

 

컨트롤러가 initBinder의 도움을 받으려면 pom.xml에 spring-boot-starter-validation 라이브러리를 추가해야함

spring-boot-starter-validation 라이브러리의 URL을 알려면 역시나 Maven Repository에서 라이브러리 명으로 검색하면 됨

 

Maven Repository 에서 다음과 같이 spring-boot-starter-validation 라이브러리를 검색(1) -> 첫 번째 검색 결과(2)로 이동 -> 가장 최상위 버전(3) 을 선택하자

그 후 라이브러리 URL을 복사 한 뒤 프로젝트 내 pom.xml 의  닫는 dependency ( </dependency> ) 의 바로 위에 붙여 넣자

그 후 version 태그는 삭제하자

 

spring-boot-starter 로 시작하는 라이브러리의 경우 version 태그가 이미 프로젝트 설정에 지정되어있으므로 우리가 지정하지 않는게 좋음

우리가 지정하면 프로젝트에 설정된 version 태그와 우리가 지정한 version 태그가 충돌이 발생해 문제가 생길 수 있음


initBinder 를 사용하기 위해 라이브러리를 추가했다면 이제 컨트롤러가 initBinder의 도움을 받아 Validator(의존 객체)를 생성하도록 만들어보자

 

ValidationController 클래스에 다음과 같이 type2 컨트롤러와 initBinder 메서드를 추가하자

package com.study.chapter02;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PostMapping;

import jakarta.validation.Valid;

@Controller
public class ValidationController {
	@PostMapping("/chapter02/validation/type2")
	public void type2(@Valid Member member, BindingResult errors) {
		if(errors.hasErrors()) {
			// 커맨드 객체 검증에 실패했을 때
			// 필요한 처리
		}
		
		if(errors.hasFieldErrors("id")) {
			// 커맨드 객체의 특정 값의 검증에 실패했을 때
			// 필요한 처리
		}
		
		// ...
	}
	
	@InitBinder
	protected void initBinder(WebDataBinder binder) {
		binder.setValidator(new MemberValidator());
	}
	
	@PostMapping("/chapter02/validation/type1")
	public void type1(Member member, BindingResult errors) {
		Validator validator = new MemberValidator();
		
		validator.validate(member, errors);
		
		if(errors.hasErrors()) {
			// 커맨드 객체 검증에 실패했을 때
			// 필요한 처리
		}
		
		if(errors.hasFieldErrors("id")) {
			// 커맨드 객체의 특정 값의 검증에 실패했을 때
			// 필요한 처리
		}
		
		// ...
	}
}

<< 코드 설명 >>

 

initBinder의 도움 없이 의존 객체를 직접 생성하는 강한 결합(왼쪽)과 initBinder의 도움으로 의존 객체를 생성하는 약한 결합(오른쪽)을 비교해보자

(1). 의존 객체를 직접 생성함

(2). 의존 객체를 직접 생성했으므로 커맨드 객체를 검증하기 위해 validate 메서드를 직접 호출함

(3). 의존 객체를 initBinder가 대신 생성함

  이때 initBinder 메서드 안에 binder.setValidator 메서드의 인자를 보면 type2 컨트롤러에서 필요한 Validator를 생성해 인자로 넣었음

  우리 눈에는 안보이지만 initBinder를 통해 의존 객체를 생성 한 후 type2 컨트롤러에게 전달한 것

(4). 컨트롤러가 의존 객체를 직접 생성하지 않았으므로 커맨드 객체 검증을 할 때도 우리가 직접 하는게 아님

  검증 하고 싶은 커맨드 객체에 @Valid 애너테이션을 달아주면 MemberValidator에 선언한 validate메서드가 적절하게 알아서 호출됨

 

Spring Framework의 장점은 애너테이션을 통해 개발자가 입력해야되는 코드의 양을 상당 부분 줄일 수 있다는 것

그 대신 각 애너테이션이 어떤 역할인지 애너테이션 간에 서로 어떤 관계가 있는지를 확실히 알아야함

initBinder의 도움을 받는것도 마찬가지

왼쪽에 있는 type1 컨트롤러처럼 initBinder의 도움 없이 커맨드 객체를 검증할 수 있지만 좋지 못한 코드가 될 가능성이 높기 때문에 오른쪽에 있는 type2 컨트롤러처럼 initBinder의 도움으로 커맨드 객체를 검증하는 것

type2 컨트롤러처럼 의존 객체와 약하게 결합 되어있다면 좋은 코드가 될 가능성이 높음

그 대신 @InitBinder 애너테이션과 @Valid 애너테이션에 대해서 확실히 알고 있어야함


전 글 ( https://codingaja.tistory.com/109 ) 부터 여기까지 배운 커맨드 객체를 검증하는 과정을 순서대로 나열해보자

1. 검증하고 싶은 커맨드 객체를 검증할 Validator 클래스를 생성함

  이때 Validator 클래스의 이름은 보통 (커맨드객체명)Validator 로 지어줌

  또한 Validator 클래스는 반드시 Validator 인터페이스를 구현해야함

2. Validator 인터페이스를 구현할 때는 supports, validate 메서드를 오버라이딩 해야함

  supports 메서드는 이 Validator가 어떤 커맨드 객체를 검증할 지 Spring Framework에게 알려주는 메서드

  단순히 이름이 (커맨드객체명)Validator 라고 해서 Spring Framework에서 어떤 커맨드 객체를 검증할 Validator 인지 알 수 없음

  validate 메서드는 이 Validator가 커맨드 객체를 검증하는 메서드로 이 메서드에 검증 하는 코드를 구현하면 됨

3. Validator 클래스가 준비됐다면 컨트롤러에서 Validator를 사용해 커맨드 객체를 검증해야하는데 이때 InitBinder의 도움으로 컨트롤러가 Validator 를 생성하도록 해야함

4. 컨트롤러가 갖고 있는 커맨드 객체에 @Valid 애너테이션을 붙여서 Validator로 커맨드 객체를 검증하도록 함


이제 프로젝트를 실행시키고 postman 으로 type2 컨트롤러에게 다양한 값을 보내 커맨드 객체가 제대로 검증 되는지 확인해보자


이제 마지막으로 커맨드 객체 검증 코드를 더 더 줄여보자

이 방법은 애너테이션을 사용해 Validator의 코드를 더 줄이는 방법으로 만약 지금까지 커맨드 객체 검증 방법이 헤깔리거나 어렵다고 느껴진다면 이 아랫부분은 안보고 여기서 마무리 지어도 됨

 

@Valid 애너테이션이 붙은 커맨드 객체의 멤버 변수에 @NotNull, @NotEmpty, @Size 등과 같은 애너테이션을 사용할 수 있고 이 애너테이션들을 사용하면 커맨드 객체를 검증할 Validator의 코드가 더 줄어듬

 

프로젝트 -> com.study.chapter02 -> Member 클래스 내 다음과 같이 애너테이션들을 추가하자

package com.study.chapter02;

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

@Data
public class Member {
	@NotNull(message="empty")
	@NotEmpty(message="empty")
	@Size(min=3, max=20, message="too short or long")
	private String id;
	
	@NotNull(message="empty")
	@NotEmpty(message="empty")
	@Size(min=6, max=20, message="too short or long")
	private String pw;
	
	// 커맨드 객체를 검증하기 위한 애너테이션들
	private String nickname;
	
	// 커맨드 객체를 검증하기 위한 애너테이션들
	private String[] hobby;
}

그리고 프로젝트 -> com.study.chapter02 -> MemberValidator 클래스 내 코드를 다음과 같이 대폭 줄이자

package com.study.chapter02;

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

public class MemberValidator implements Validator {
	@Override
	public boolean supports(Class<?> clazz) {
		return Member.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		Member member = (Member) target;
		
		// 커맨드 객체 검증 코드1
		// 커맨드 객체 검증 코드2
		// 커맨드 객체 검증 코드3
		// ...
		// 커맨드 객체 검증 코드n
	}
}

<< 코드 설명 >>

먼저 MemberValidator 클래스부터 설명하면 MemberValidator 클래스 내 validate 메서드의 코드가 싹 사라졌음

이는 이를 대신할 애너테이션을 Member 클래스에 사용했기 때문임

 

MemberValidator와 Member 를 서로 비교해보자

(1), (2). 커맨드 객체의 멤버 변수가 null이거나 빈 문자열일 경우 커맨드 객체 검증 실패로 처리했는데 이 역할을 @NotNull, @NotEmpty 애너테이션이 대신하도록 한 것

  @NotNull은 애너테이션명 그대로 해당 멤버 변수의 값이 null 이라면 커맨드 객체 검증에 실패하고 사유로 "empty"를 남김

  @NotEmpty는 해당 멤버 변수의 값이 빈문자열("") 이거나 공백문자만 들어있는 문자열이라면 커맨드 객체 검증에 실패하고 실패 사유로 "empty"를 남김

  검증 실패 사유를 바꾸고 싶다면 message 속성의 값을 바꾸면 됨

 

 

(1), (2). 커맨드 객체의 멤버 변수 값이 너무 짧거나 너무 길다면 커맨드 객체 검증 실패로 처리했는데 이 역할을 @Size 애너테이션이 대신하도록 한 것

  @Size는 애너테이션명 그대로 해당 멤버 변수의 값이 일정 길이 미만, 초과라면 커맨드 객체 검증에 실패하고 사유로 "too short or long" 을 남기는 것

  해당 멤버 변수 값의 길이가 min, max를 포함해 min부터 max 사이라면 커맨드 객체 검증에 성공하고 min 미만 또는 max 를 초과했다면 커맨드 객체 검증에 실패함

 

이렇게 @Valid 애너테이션이 붙은 커맨드 객체는 애너테이션을 추가적으로 더 사용해 개발자가 입력해야할 코드를 많이 줄일 수 있음

 

여기서 한가지 단점은 @Valid 애너테이션을 사용했고 커맨드 객체에서 @NotNull, @NotEmpty 등의 애너테이션을 사용해 커맨드 객체를 검증한다면 initBinder가 동작하지 않도록 주석 처리해야함

 

커맨드 객체 안에서 애너테이션을 사용해서 커맨드 객체를 검증할 것이냐 initBinder를 통해서 생성한 Validator로 검증을 할 것이냐 선택해야한다는 것

나중에 포트폴리오용 프로젝트 등을 만들면 이는 큰 걸림돌이 될텐데 Spring-Boot 에서는 이정도만 배우자

Spring Framwork 포스팅을 올릴 때가 온다면 거기서 커맨드 객체 검증(Validation)에 대해서 자세히 올릴 예정임

 

커맨드 객체를 검증할 때 initBinder 를 통해서 Validator 를 생성하고 검증하는 방법과 커맨드 객체 안에서 애너테이션으로 검증하는 방법이 있으니 적절히 필요한 것들을 골라서 사용하면 됨

728x90
LIST