BE/Spring & Spring Boot

커스텀 어노테이션

baek-dev 2026. 1. 5. 01:13

회원가입할때 DTO로 validation을 하는데, password 와 password2 를 비교할때,
service에서 password.equals(passowrd2) 를 사용하는것은 좋지 않음.

  • API마다 중복
  • 테스트 어려움
  • 검증 책임 혼재

같은 이유때문.

 

 

JoinDto

@Getter
@NoArgsConstructor
@AllArgsConstructor
@PasswordMatch
public class JoinRequest {

    @NotBlank
    @Size(min = 4)
    @Pattern(
            regexp = "^[a-zA-Z0-9]+$",
            message = "아이디는 영문 대소문자와 숫자만 포함할 수 있습니다."
    )
    private String username;

    @NotBlank
    @Size(min = 8)
    @Pattern(
            regexp = "^(?=.*[!@#$%^&*(),.?\":{}|<>]).+$",
            message = "비밀번호에는 최소 8자이며, 최소 하나의 특수문자가 포함되어야 합니다."
    )
    private String password;

    @NotBlank
    private String password2;

    @NotBlank
    @Pattern(
            regexp = "^[^\\s]+$",
            message = "닉네임에는 공백을 포함할 수 없습니다."
    )
    private String nickname;

    @NotBlank
    @Email
    private String email;
}

 

 

1. PasswordMatch.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordMatchValidator.class)
public @interface PasswordMatch {
    String message() default "비밀번호가 일치하지 않습니다.";

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

    Class<? extends Payload>[] payload() default {};
}
  1. @Target(ElementType.TYPE)
    1. FIELD, METHOD X
    2. CLASS O
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Constraint(validatedBy = PasswordMatchValidator.class)
    1. 이 어노테이션이 붙은 대상을 검증할 때, PasswordMatchValidator를 실행해라 라는 의미
  4. @interface
    1. annotation interface 라는 의미
    2. 일반 인터페이스가 아닌 어노테이션용 인터페이스라는 의미

PasswordMatch 가 규칙이름이라면, PasswordMatchValidator는 실제 로직.

 

 

2. PasswordMatchValidator.java

public class PasswordMatchValidator
        implements ConstraintValidator<PasswordMatch, JoinRequest> {

    @Override
    public boolean isValid(JoinRequest value, ConstraintValidatorContext context) {
        if (value.getPassword() == null || value.getPassword2() == null) {
            return true;
        }
        return value.getPassword().equals(value.getPassword2());
    }
}
  1. ConstraintValidator<A, T>
    1. A - 어떤 어노테이션을 처리할지,
      T - 어떤 타입을 검증할지
    2. PasswordMatch 어노테이션이 붙은 JoinRequest 객체를 검증하겠다

isValid 메서드가 실행되는 순서.

  1. 클라이언트 → /join 요청
  2. JSON → JoinRequest 객체 생성
  3. @Valid 발견
  4. JoinRequest에 붙은 검증 어노테이션 수집
    • @NotBlank
    • @Pattern
    • @PasswordMatch
  5. @PasswordMatch 발견
  6. @Constraint 확인
  7. PasswordMatchValidator 인스턴스 생성
  8. isValid() 호출

검증이 실패하면 GlobalExceptionHandler로 이동.


@Target 의 ElementType 종류

ElementType 의미 예시
TYPE 클래스, 인터페이스, enum class, interface
FIELD 필드(멤버 변수) private String name;
METHOD 메서드 public void save()
PARAMETER 메서드 파라미터 (@Valid String name)
CONSTRUCTOR 생성자 public User()
LOCAL_VARIABLE 지역 변수 String tmp;
ANNOTATION_TYPE 어노테이션 @interface MyAnno
PACKAGE 패키지 package-info.java
TYPE_PARAMETER 제네릭 타입 파라미터 <T>
TYPE_USE 타입이 사용되는 모든 위치 List<@NotNull String>
MODULE 모듈 (Java 9+) module-info.java
RECORD_COMPONENT record 필드 (Java 16+) record User(String name)

 

'BE > Spring & Spring Boot' 카테고리의 다른 글

LogBack  (0) 2026.01.23
@ConfigurationProperties, @ConstructorBinding  (0) 2025.05.22
@Value  (0) 2025.05.21
@ElementCollection  (0) 2025.05.20
[Spring Boot] HttpClient  (0) 2025.05.12