JAVA && Spring

Custom javax validation constraints, 동적으로 Class 레벨의 validate 및 Custom 에러 메시지 (field, message)

도미노& 2021. 8. 25. 14:12

* 연관 검색어 : javax validation constraints, java bean validation, hibernate validation, 동적 validation

 

※ 예시 소스는 실제 소스에서 이름을 변경해 작성한 것이기 때문에 복사+붙여넣기를 할 경우 에러가 날 수도 있습니다.
에러가 난다면 적절하게 수정해서 사용 부탁드립니다.

 

 


구현 중인 것은 도메인 모델(Domain model)에서 Request 값의 유효성 체크.
보통 어노테이션(Annotation)을 쓰면 모든 field에 대해 체크한다.

예시 모델을 보자면, 내가 구현하고 싶은 기능은
is_admin이 true라면 email을 필수값 체크, 아니면 체크하지 않는 것이다.


▼ 예시 모델 (UserDomain.java)

public class UserDomain {

    @NotBlank
    @Schema(title = "User Type")
    private String user_type;
    
    @NotBlank
    @Size(min = 1, max = 50)
    @Schema(title = "User ID")
    private String user_id;

    @Schema(title = "Is Admin", type = "boolean", defaultValue = "false")
    private Boolean is_admin = false;

    @Size(min = 4, max = 100)
    @Email(message = "Email format is invalid.")
    @Schema(title = "Email Address")
    private String email;

}

 

▼ 예시 Interface. 동적으로 체크를 해야 하기에 Validator를 Customizing 하기 위함.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UserValidatorImpl.class)
@Documented
public @interface UserValidator {
    String message() default "Value is not valid.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

 

@Target을 ElementType.TYPE 로 썼기 때문에
위의 interface를 상속받아 만들어진 Validator는 Class에 선언한다.

만약 Column에 선언하고 싶으면 ElementType.FIELD로 사용하면 된다.
@Target({ElementType.TYPE, ElementType.FIELD})처럼 사용할 수도 있다.



▼ 예시 Validator. 위의 Interface를 상속받아 내가 체크하고자 하는 Validator를 구현한다.

public class UserValidatorImpl implements ConstraintValidator<UserValidator, UserDomain> {

    @Override
    public void initialize(UserValidator validator) {
    }
    
    @Override
    public boolean isValid(UserDomain user, ConstraintValidatorContext context) {

        if (user == null) {
            return true;
        }
        
        boolean isValid = true;
        
        if (user.getIs_admin()) { // is_admin이 true일 때
            
            if("".equals(CommonUtil.nullToString(user.getEmail()).trim())) {
                isValid = false;
                context.disableDefaultConstraintViolation();
                context.buildConstraintViolationWithTemplate("must not be blank").addPropertyNode("email").addConstraintViolation();
            }
        }
        
        return isValid;
    }
}

 



이런 식으로 내가 필요한 검증을 구현할 수 있다.
그리고 아래와 같이 도메인에 위에 @UserValidator로 사용할 수 있다.

@UserValidator
public class UserDomain {

    @NotBlank
    @Schema(title = "User Type")
    private String user_type;
    
    @NotBlank
    @Size(min = 1, max = 50)
    @Schema(title = "User ID")
    private String user_id;

    @Schema(title = "Is Admin", type = "boolean", defaultValue = "false")
    private Boolean is_admin = false;

    @Size(min = 4, max = 100)
    @Email(message = "Email format is invalid.")
    @Schema(title = "Email Address")
    private String email;

}

 

 

▼ Postman과 같은 툴로 테스트를 했을 때 결과 화면


어느 필드에서 에러가 났는지 알려주기 위해
UserValidateImpl.java에서 Context에 에러 메시지를 설정해 주는데
구체적일 필요가 없다면 context를 수정하지 않고
Domain 위에 @UserValidator(message = "원하는 메시지") 로 표현해 줄 수도 있다.




※ 오늘 본문은 아래 출처들을 참고해 직접 작성했다.
https://meetup.toast.com/posts/223
https://devonce.tistory.com/42
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-defineconstraints-hv-constraints
https://kapentaz.github.io/spring/Spring-Boo-Bean-Validation-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90/
https://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/validator-customconstraints.html#section-cross-parameter-constraints
https://ch4njun.tistory.com/222