Bean들의 유효성 검증을 위한 API
- Bean의 유효성 검증은 매우 중요하다. 각 Bean마다 유효성 검증 로직을 구현하기에는 비효율 적이기에 Bean Validation API를 통해서 간단하게 유효성 검증을 하도록 설계할 수 있다.
- 즉, Domain-Model 제약 조건을(Constraints) 선언함으로써 Validation 로직을 여러 레이어에서 분리 시키려는 것이 Bean Validation API의 핵심 목표이다.

Bean Validation API 유효성 검증 관련 메커니즘
아래의 구성요소를 따르면 보다 직관적으로 유효성 검증을 하는 것을 볼 수 있다.
[제약 조건]
- Domain Model에 Contraint 어노테이션을 부여한다.
- 해당 Constraint 에 명시된 유효성 검증 로직(isValid())를 수행한다.
[유효성 검증]
- ValidatorFactory를 이용하여 Validator 를 생성한다.
- Validator를 이용하여 유효성 검증을 수행한다.
- 유효성 검증에 실패한 내용에 대한 정보를 가진 ConstraintViolation을 통해 비즈니스 로직을 처리한다.
@Constraint
유효성 검증 대상을 표시하고, 제약 조건을 부여하는 역할이다.
- 제약 조건을 정의하는 어노테이션의 조건
- 필수 속성이 모두 존재야한다.
- message : 제약 조건 위배시 생성되는 에러 메시지, 국제 표준 resource bundle key(해당 클래스의 경로 + .message) 권장
- group: 유효성 검증을 수행할 그룹을 지정한다. (default = Empty Group)
- payload: 유효성 검증 수행시 클라이언트가 사용할 수 있는 메타 데이터를 지정, Payload 권장
- 추가 : validationAppliesTo - 제약 조건 어노테이션 단에서 검증 대상을 선택적으로 설정하고, 같은 어노테이션으로 객체와 매개변수 검증을 유연하게 처리하고 싶을 때 사용합니다.
- IMPLICIT (default) - 어노테이션이 지정된 대상을 검증대상으로 지정
- RETURN_VALUE - 반환값을 유효성 검증대상으로 지정
- PARAMETERS - 매개변수를 유효성 검증대상으로 지정
- 추가 : 만약 @SupportedValidationTarget과 다르게 쓰였을 경우 constraintValidator는 동작하지 않는다.
- @SupportedVaidationTargert
- Bean Validation 의 대상을 지정하는 어노테이션이다.
- 어노테이션의 적용범위를 설정하는 @Target과 ConstraintValidator 에서 검증을 할 값을 지정해주는 것이다.
- 지정 타입
- ValidationTarget.ANNOTATED_ELEMENT : 어노테이션이 지정된 전체 객체를 대상
- ValidationTarget.PARAMETERS : 메서드의 매개변수들을 대상
- @SupportedVaidationTargert
- valid라는 속성명이 존재해서는 안된다.
- 예시
@Documented
@Constraint(validatedBy = {ExampleConstraintValidator.class})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@Target(value = {METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExampleConstraint {
//필수 요소
String message() default "{com.example.springSecurity.config.security.validation.ExampleConstraint.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//추가 요소
ValidationTarget validationAppliesTo() default ValidationTarget.ANNOTATED_ELEMENT;
String test() default "test";
}
내부 로직
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {
Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}
- Annotation 에만 적용되도록 하고 있으며, Runtime 동안 동작하도록 설정한다.
- validatedBy() 부분 같은 경우 실질적인 검증 로직을 수행하는 ConstraintValidator를 상속받는 클래스를 입력받고 있다.
ConstraintValidator
Constraint 설정이 된 객체의 초기화 및 유효성 검증을 수행하도록 정의한다.
- 내부로직
- initialize() 같은 경우는 필요한 경우 오버라이드 하여 사용할 수 있도록 되어있다.
- 가장 중요한 isValid 의 경우
public interface ConstraintValidator<A extends Annotation, T> {
default void initialize(A constraintAnnotation) {
}
boolean isValid(T var1, ConstraintValidatorContext var2);
}
- 예시
public class ExampleConstraintValidator implements ConstraintValidator<ExampleConstraint, String> {
@Override
public void initialize(ExampleConstraint constraintAnnotation) {
//초기화
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
//검증 로직
return true;
}
}
Validator
유효성 검증로직을 수행하여 ConstraintViolation에 유효성 검증에서 실패한 것들을 담아 반환한다.
- 내부로직
- Set<ConstraintViolation<T>> validate() : 객체에 부여된 모든 제약조건에 대해 유효성 검증
- Set<ConstraintViolation<T>> validateProperty() : 객체의 특정 프로퍼티에 대해 유효성 검증
- Set<ConstraintViolation<T>> validateValue() : 객체의 특정 프로퍼티의 특정 값에 대해 유효 검증
public interface Validator {
<T> Set<ConstraintViolation<T>> validate(T var1, Class<?>... var2);
<T> Set<ConstraintViolation<T>> validateProperty(T var1, String var2, Class<?>... var3);
<T> Set<ConstraintViolation<T>> validateValue(Class<T> var1, String var2, Object var3, Class<?>... var4);
BeanDescriptor getConstraintsForClass(Class<?> var1);
<T> T unwrap(Class<T> var1);
ExecutableValidator forExecutables();
}
- 예시
- Validation 클래스를 통해 DefaultValidatorFactory를 생성하여 Validator를 생성한다.
- validator의 검증로직(validate)을 사용하여 검증을 통과하지 못한 오류들을 Validation에 담는 것을 볼 수 있다.
- 검증을 통과하지 못한 오류가 하나라도 있는 경우 예외를 던지는 것을 볼 수 있다.
public abstract class SelfValidating<T> {
private final Validator validator;
public SelfValidating() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
/**
* Evaluates all Bean Validations on the attributes of this
* instance.
*/
protected void validateSelf() {
Set<ConstraintViolation<T>> violations = validator.validate((T) this);
if (!violations.isEmpty()) {
log.error("Validation error occurred: {}", violations);
throw new CommonException(ErrorCode.INTERNAL_DATA_ERROR);
}
}
}