공부/Spring

[공부/Spring] 유효성 검사를 위한 Validation 처리

yulee_to 2023. 3. 7. 01:05

스프링

운영체제 : MacOS M1
IDE : IntelliJ
Spring Boot : 2.7.8
Java : 11
JDBC : MyBatis

계기

회원가입의 경우 아이디, 비밀번호, 이메일 등 그 값이 일정한 형식을 지켜야 한다. 이를 체크해주는 메소드를 하나 하나 생성해줄 수도 있겠지만 이전에 @NotNull, @Email 등 유효성 검사를 해주는 어노테이션을 본 적이 있어 해당 어노테이션들을 이용해 유효성을 검사하고자 한다.



@Valid란?

@Valid는 JSR-303 표준 스펙으로써 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이다. 어노테이션으로 편리하게 검증을 해준다는 특징이 있다. 

Spring에서는 LocalValidationFactoryBean이 제약 조건 검증을 처리해준다. 이를 이용하기 위해 LocalValidatorFactoryBean을 빈으로 등록해야 하는데 SpringBoot에서는 build.gradle에 다음 코드를 추가해주면 해당 기능들이 자동 설정된다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

 

@Valid 사용방법

@Getter
public class SignUpReq {

	@NotBlank
	@Pattern(message = "잘못된 아이디 형식입니다."
		, regexp = "^[a-z0-9_-]{3,10}")
	private String userId;

	@NotBlank
	@Pattern(message = "잘못된 비밀번호 형식입니다."
		, regexp = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[$@$!%*#?&])[A-Za-z[0-9]$@$!%*#?&]{8,15}")
	private String password;

	@NotBlank
	@Email(message = "잘못된 이메일 형식입니다.")
	private String email;
}

먼저 유효성을 검사하고 싶은 객체를 하나 만들고 각 필드에 검사할 유효성 어노테이션을 붙여주면 된다. 

@NotBlank

- null과 "", " " 모두 허용하지 않는 어노테이션이다.

- 회원가입 요청시 아이디와 비밀번호 이메일이 반드시 입력되어야 한다는 의미이다.

@Pattern

- 정규표현식을 지정해주는 어노테이션으로 message에 지정한 문자열은 정규표현식 조건에 맞지 않으면 예외 메시지로 출력된다. 

@Email

- 이메일의 경우 자주 사용하기에 따로 어노테이션으로 지정되어 있다. 

 

위 클래스에선 @Valid가 안보인다. 그럼 @Valid는 어디서 사용할까?

다음 Controller 클래스를 보자.

@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
	private final UserService userService;

	@PostMapping("/sign-up")
	public void signUp(@RequestBody @Valid SignUpReq signUpReq) {
		userService.signUp(signUpReq);
	}
}

유효성을 체크할 객체를 "검사하겠다"는 의미로 @Valid를 붙여준다.

모든 요청은 프론트 컨트롤러인 디스패처 서블릿을 통해 컨트롤러로 전달되는데 전달 과정에서 컨트롤러 메소드의 객체를 만들어주는 ArgumentResolver가 동작한다. @Valid 역시 ArgumentResolver에 의해 처리된다. 따라서 @Valid는 기본적으로 컨트롤러에서만 동작하며 다른 계층에서는 검증이 안된다. 다른 계층에서 유효성 검사를 하기 위해서는 @Validated를 사용해야 한다. 

 

@Valid 예외 

검증에 오류가 생기면 MethodArgumentNotValidException 예외가 발생한다. 

디스패처 서블릿에 기본으로 등록된 예외 리졸버인 DefaultHandlerExceptionResolver에 의해 400 Bad Request 에러가 발생한다.

나는 이전 포스팅에 다룬 ExceptionHandler로 해당 예외를 처리해주었다. 

 

@Validated란?

컨트롤러가 아닌 다른 계층에서 유효성 검사를 하고 싶을 때는 @Validated를 사용하면 된다. @Validated는 AOP 기반으로 메소드의 요청을 가로채서 유효성 검사를 진행해준다. @Validated는 JSR 표준 기술이 아니며, Spring 프레임워크에서 제공하는 어노테이션이다. 

@Service
@Validated
public class UserService {
    public void createUser(@Valid SignUpReq signUpReq) {
    }
}

@Validated를 서비스 클래스 위에 선언하고 유효성 검사를 하고 싶은 클래스에 @Valid를 붙여주면 된다.

다른 기능으로는 유효성 검증 그룹을 지정해줄 수 있다. 동일한 클래스에 대해 제약 조건이 다른 경우 제약 조건이 적용될 검증 그룹을 지정할 수 있는데 그룹 지정을 위해선 내용이 없는 마커 인터페이스를 간단히 정의해줘야 한다. 그리고 제약 조건 어노테이션(@NotNull, @Email 등)의 괄호 안에 "groups=클래스명"을 지정해주면 된다. 그룹 지정 기능은 코드가 복잡해져 거의 사용이 안되므로 그냥 참고만 하자.

 

@Validated 예외

유효성 검사에 실패하면 @Valid만 사용하던 것과는 달리 ContraintViolationException 예외가 발생한다. 

@Validated를 클래스 레벨에서 선언하면 클래스에 유효성 검사를 위한 AOP 어드바이스 또는 인터셉터가 등록된다. 그리고 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채 유효성 검증을 해주는 식으로 동작한다. 

따라서 @Validated는 계층과 무관하게 스프링 빈이라면 유효성 검증을 할 수 있다. 

 

@RequestParam과 유효성 검사

굳이 요청받을 내용을 클래스로 만들지 않고 @RequestParam으로 받아오고 싶을 때는 유효성 검사를 어떻게 해야 할까?

이때도 @Validated를 사용하면 된다. 

@RestController
@RequiredArgsConstructor
@Validated
@RequestMapping("/users")
public class UserController {
	private final UserService userService;

	@GetMapping("/id-check")
	public void idCheck(
		@RequestParam
		@Pattern(message = "잘못된 아이디 형식입니다."
			, regexp = "^[a-z0-9_-]{3,10}")
		String userId) {
		userService.userIdDuplicationCheck(userId);
	}
}

@Validated를 Controller 클래스에 붙여주고, 검사하고 싶은 파라미터에 직접 제약 조건 어노테이션을 붙여주면 된다. 

 

정리

@Valid

- JSR-303 자바 표준

- 컨트롤러 메소드의 유효성 검사만 가능

- 유효성 검사 실패시 MethodArgumentNotValidException 발생

@Validated

- 스프링이 제공하는 기능

- 컨트롤러를 포함한 다른 계층에서도 유효성 검사 가능

- 클래스에는 @Validated를, 메소드에는 @Valid를 붙여주어야 함

- @RequestParam에 대한 유효성 검사를 할 때도 사용

- 유효성 검사 실패시 ConstraintViolationException 발생

 

참고 자료

https://mangkyu.tistory.com/174

728x90