Spring

Valid 어노테이션 커스텀해서 사용하기

devkimc 2023. 10. 22. 15:18

RequestBody의 값을 검증하기 위해서 @Valid를 사용합니다.

대부분의 예시를 보면, Validator 를 커스텀해서 어노테이션 형태로 사용합니다.

 

저는 ChannelTypeValidator 를 만들어서 사용했습니다. APP, PC, MOBILE 등 다양한 채널로 유저가 유입될 수 있습니다.
세가지의 채널을 모두 검사할 수도 있지만, 1~2 종류의 채널만 허용할 수도 있습니다.

 

그런 상황일 때 Validator 를 추가로 생성하지 않고 구현 클래스 안에서 분기 처리를 하도록 커스텀했습니다.

 

예시

Validator Interface

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ChannelTypeValidator.class)
public @interface ChannelTypeCheck {
    String message() default "채널 타입이 유효하지 않습니다.";

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

    Class<? extends Payload>[] payload() default {};

    String code();
}

DTO 에서 ChannelType 을 검증할 커스텀 어노테이션입니다.

code 라는 변수를 추가로 정의했습니다. 나머지 변수는 ConstraintValidator 에 정의되어 있습니다.
code를 통해 유효성 검사 로직을 분기해서 처리합니다.

 

Validator Implement

public class ChannelTypeValidator implements ConstraintValidator<ChannelTypeCheck, Object> {
    private String message;
    private String code;

    @Override
    public void initialize(ChannelTypeCheck constraintAnnotation) {
        message = constraintAnnotation.message();
        code = constraintAnnotation.code();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        boolean flag = true;

        if ("VALID_CODE_APM".equals(code)) {
            if (!value.equals("APP") && !value.equals("PC") && !value.equals("MOBILE")) {
                flag = false;
            }
        }

        if ("VALID_CODE_AP".equals(code)) {
            if (!value.equals("APP") && !value.equals("PC")) {
                flag = false;
            }
        }
        return flag;
    }
}

ConstraintValidator 를 상속받아 실질적으로 유효성 검사 분기 로직을 처리하는 구현부입니다.
정의한 code 를 체크하고 code 에 따른 유효성 검사를 수행합니다.

 

DTO 1

@Getter
@Builder
@AllArgsConstructor
public class TransferAccountDTO {

    @ChannelTypeCheck(code = "VALID_CODE_APM", message = "채널 타입이 유효하지 않습니다. (APP, PC, MOBILE)")
    private String channelType;

    private Long sendUserId;

    private Long receiveUserId;

    private int money;
}

모든 ChannelType 을 검사하는 DTO 입니다. code, message 를 정의합니다.

 

DTO 2

@Getter
@Builder
@AllArgsConstructor
public class TransferAccountValidTestDTO {

    @ChannelTypeCheck(code = "VALID_CODE_AP", message = "채널 타입이 유효하지 않습니다. (APP, PC)")
    private String channelType;

    private Long sendUserId;

    private Long receiveUserId;

    private int money;
}

모든 APP, PC 채널 타입만 검사하는 DTO 입니다. code, message 를 정의합니다.

 

Controller

@RestController
@RequiredArgsConstructor
public class AccountController {

    private final TransferService transferService;

    // 1. 채널 타입이 APP, PC, MOBILE 중 하나인지 체크하는 경우
    @PostMapping("/account/apm")
    public ResponseEntity<Object> transferAccount(@Valid TransferAccountDTO TransferAccountDTO) {
        transferService.transferToAccount(TransferAccountValidTestDTO);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    // 2. 채널 타입이 APP, PC 중 하나인지 체크하는 경우
    @PostMapping("/account/ap")
    public ResponseEntity<Object> transferAccount(@Valid TransferAccountValidTestDTO transferAccountValidTestDTO) {
        transferService.transferToAccount(TransferAccountValidTestDTO);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}