회원가입할때 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 {};
}
- @Target(ElementType.TYPE)
- FIELD, METHOD X
- CLASS O
- @Retention(RetentionPolicy.RUNTIME)
- @Constraint(validatedBy = PasswordMatchValidator.class)
- 이 어노테이션이 붙은 대상을 검증할 때, PasswordMatchValidator를 실행해라 라는 의미
- @interface
- annotation interface 라는 의미
- 일반 인터페이스가 아닌 어노테이션용 인터페이스라는 의미
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());
}
}
- ConstraintValidator<A, T>
- A - 어떤 어노테이션을 처리할지,
T - 어떤 타입을 검증할지 - PasswordMatch 어노테이션이 붙은 JoinRequest 객체를 검증하겠다
- A - 어떤 어노테이션을 처리할지,
isValid 메서드가 실행되는 순서.
- 클라이언트 → /join 요청
- JSON → JoinRequest 객체 생성
- @Valid 발견
- JoinRequest에 붙은 검증 어노테이션 수집
- @NotBlank
- @Pattern
- @PasswordMatch
- @PasswordMatch 발견
- @Constraint 확인
- PasswordMatchValidator 인스턴스 생성
- 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 |