@Valid

자바에서 사용되는 어노테이션 중 하나로, HTTP 요청 데이터를 객체로 반환할 때 유효성 검사를 수행하는 어노테이션이다.

LocalValidatorFactoryBean

스프링에서 제공하는 Validator 인터페이스의 구현체 중 하나이다. 스프링 어플리케이션 컨텍스트 내에서 사용되며 @Valid 어노테이션을 통한 데이터 유효성 검사를 처리한다. 스프링에서는 validation 의존성만 추가해주면 자동으로 빈 등록이 되어 사용할 수 있다.

사용법

public class SignupRequestDto {
	@NotBlank
	private String username;
	@NotBlank
	private String password;
}

@PostMapping("/user/signup")
	public ResponseEntity<Void> signup(@Valid @RequestBody SignupRequestDto requestDto) {
		return ResponseEntity.ok().build();
}

유효성을 검증할 데이터를 받아올 DTO를 생성하고, 각 필드에 검증하고 싶은 유효성 어노테이션을 붙여준다. 해당 DTO에 @Valid 어노테이션을 붙이면 유효성 검증을 할 수 있다.

검증 실패

검증에 실패할 경우 MethodArgumentNotValidException 예외를 던지게 된다.

커스텀 어노테이션

회원가입 데이터의 조건

  • 사용자 이름 : 최소 4자 이상, 10자 이하이며 알파벳 소문자, 숫자로 구성되어야 한다.
  • 비밀번호 : 최소 8자 이상, 15자 이하이며 알파벳 대소문자, 숫자로 구성되어야 한다.

SignupRequestDto

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class SignupRequestDto {
	@Username
	private String username;
	@Password
	private String password;
}

@Username, @Password

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UsernameValidator.class)
@Documented
public @interface Username {

	String message() default "사용자 이름은 최소 4자 이상, 10자 이하이며 알파벳 소문자, 숫자로 구성되어야 합니다.";

	Class<?>[] groups() default { };

	Class<? extends Payload>[] payload() default { };

}

@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {

		String message() default "비밀번호는 최소 8자 이상, 15자 이하이며 알파벳 대, 소문자, 숫자로 구성되어야 합니다.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
  • @Target(ElementType.FIELD)
    • 이 어노테이션을 필드에만 적용할 수 있도록 지정
  • @Retention(RetentionPolicy.RUNTIME)
    • 런타임시에도 이 어노테이션 정보를 유지할 수 있도록 저장
  • @Constraint(validatedBy = UsernameValidator.class)
    • 이 어노테이션의 실제 유효성 검사를 수행하는 클래스를 지정
  • @Documented
    • 이 어노테이션의 정보가 Javadoc에 포함되도록 지정
  • String message() default
    • 어노테이션이 유효성 검사에 실패할 경우 사용할 디폴트 에러 메시지를 정의
  • Class<?>[] groups() default {};
    • Bean Validation에서 그룹화를 지원하기 위한 설정, 사용하지 않으면 비어있는 배열 사용
  • Class<? extends Payload>[] payload() default {};
    • 추가적인 정보를 전달할 때 사용, 사용하지 않으면 비어있는 배열 사용

UsernameValidator, PasswordValidator

@Component
public class UsernameValidator implements ConstraintValidator<Username, String> {

    private static final int MIN_SIZE = 4;
    private static final int MAX_SIZE = 10;
    private static final String regexUsername = "^[a-z0-9]+[a-z0-9]{" + MIN_SIZE + "," + MAX_SIZE + "}$";

    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        boolean isValidUsername = username.matches(regexUsername);
        if (!isValidUsername) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                    MessageFormat.format("{0}자 이상의 {1}자 이하의 숫자, 영문자를 포함한 이름을 입력해주세요.",
                            MIN_SIZE,
                            MAX_SIZE)).addConstraintViolation();
        }
        return isValidUsername;
    }
}

@Component
public class PasswordValidator implements ConstraintValidator<Password, String> {

    private static final int MIN_SIZE = 8;
    private static final int MAX_SIZE = 15;
    private static final String regexPassword = "^(?=.*[A-Za-z])(?=.*[0-9])[A-Za-z0-9]{" + MIN_SIZE + "," + MAX_SIZE + "}$";

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        boolean isValidPassword = password.matches(regexPassword);
        if (!isValidPassword) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                            MessageFormat.format("{0}자 이상의 {1}자 이하의 숫자, 대,소문자를 포함한 비밀번호를 입력해주세요", MIN_SIZE, MAX_SIZE))
                    .addConstraintViolation();
        }
        return isValidPassword;
    }
}
  • isValid() : 유효성 검사를 진행하는 로직 작성
  • context.disableDefaultConstraintViolation() : 기본 에러 메시지를 사용하지 않도록 설정
  • context.buildConstraintViolationWithTemplate() : 사용자 정의 에러 메시지를 추가
  • addConstraintViolation() : 에러가 발생하게 되면 에러 정보를 ConstraintValidatorContext에 추가하여 클라이언트에게 전달한다. 전달된 정보는 에러 메시지를 생성하는데 사용된다.

참고1 참고2