728x90
반응형

https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-customconstraints

 

Hibernate Validator 7.0.2.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th

docs.jboss.org

Spring Project에서 Validation 설정 후에 사용을 하다보면 내가 원하는 형태의 Validation이 필요할 때가 있다. 기본적으로 제공하는 것으로 부족할 때가 반드시 발생하게 된다. 그때에는 Custom Annotation을 만들고, Validator를 연결하여 Contraint Annotation을 만들어서 사용하면 된다. 

 

나의 경우에 가장 먼저 필요한 Custom Validation Contraint는 Request Body에 전달되는 Session Values가 Json String format인지 확인이 필요하였다. (일반적으로 Session data는 Client가 입력하고 싶은 데이터를 입력하여야 하기 때문에 그 값을 Json 형태로 저장해 두면 활용성이 올라갈 것 같아 이렇게 만들었다.)

 

하지만, 기본으로 제공하는 Validation에서는 없는 기능이라 Custom Contraint를 작성하게 되었다. 

 

Annotation Interface

우선 Custom Annotation을 만들기 위해서는 Annotation Interface가 필요하다. 

@Documented			// Javadoc 문서에 Annotation이 포함된다.
@Constraint(validatedBy = JsonStringValidator.class)	// Contraint를 수행하는 Validator class
@Target({ElementType.FIELD})			// Target은 Field에서만 가능하다. (DTO field)
@Retention(RetentionPolicy.RUNTIME)		// 이 Annotation이 동작되는 범위 - Source(컴파일 이후 없어짐), Class(클래스 참조시까지), Runtime(컴파일 이후에도 가능)
public @interface JsonStringConstraint {
    String message() default "Invalid Json String type";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

위와 같이 JsonStringConstraint Annotation Interface를 작성하였다. 엄밀히 얘기하면 Custom Contraint용 Annotation이다. 

Contraint를 작성하기 위해서는 3가지 요소가 반드시 필요한데, message(), groups(), payload()가 그것이다. 

 

  • message - Validation이 실패하였을 경우 표시하는 메세지이다. 이것은 Message Source Accessor과 함께 사용할 수 있다. 
  • groups - Contraint를 Groupping 하는 기능이다. 그룹별로 Message Source를 다르게 사용할 때 등에 사용된다. 
  • payload - 이 값은 Validator에 전달하고 싶은 값을 넣는 곳이다. 예를 들면, Contraint 의 심각도 등을 보내어 심각도에 따라 다른 행위를 하도록 할 수 있다. 

 

JsonStringValidator class

@Slf4j
public class JsonStringValidator implements ConstraintValidator<JsonStringConstraint, String> {

    @Override
    public void initialize(JsonStringConstraint constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        try {
            if (s != null) {
                final ObjectMapper mapper = new ObjectMapper();
                mapper.readTree(s);
            }
            return true;
        } catch (IOException e) {
            LOGGER.debug("String is not json format. {}", s);
            return false;
        }
    }
}

Validator class 는 ContraintValidator Interface를 구현한다. Override되는 메서드는 initailize() 와 isValid() 가 있다. 

여기에서 중요한 것은 isValid() 이다. Contraint가 값을 확인 후에 Validation 결과를 Return 해주어야 한다. 

 

Json String이 유요한지 여부는 Jackson 에 있는 ObjectMapper를 이용하여 확인하였다. 

이 Contraint는 String Field에서만 사용이 가능하다. 

 

사용방법

사용방법은 다른 Validation annotation과 동일하게 사용가능하다. 

@Getter
@Setter
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Schema(description = "Session Creation Request")
public static class SessionCreateRequest {
    @Schema(example = "1L")
    @NotNull(message = "{account.id.empty}")
    private Long accountId;
    @Schema(example = "{\"orderCount\":1}")
    @NotBlank(message = "{session.value.empty}")
    @JsonStringConstraint(message = "{session.value.not.json}")
    private String values;
}

Validation messsage는 Message Source 기능을 사용하여 가져오도록 하였다. 그리고, 다른 Validation Annotation과 함께 사용할 수 있는 것을 볼 수 있다. 

 

아래와 같이 Validation Fail 일 경우 Error response를 받을 수 있다. 

...
"errors": [
        {
            "codes": [
                "JsonStringConstraint.sessionCreateRequest.values",
                "JsonStringConstraint.values",
                "JsonStringConstraint.java.lang.String",
                "JsonStringConstraint"
            ],
            "arguments": [
                {
                    "codes": [
                        "sessionCreateRequest.values",
                        "values"
                    ],
                    "arguments": null,
                    "defaultMessage": "values",
                    "code": "values"
                }
            ],
            "defaultMessage": "String is not JSON format.",
            "objectName": "sessionCreateRequest",
            "field": "values",
            "rejectedValue": "test",
            "bindingFailure": false,
            "code": "JsonStringConstraint"
        }
    ],
    "path": "/api/v1/session"
...

 

728x90
반응형

+ Recent posts