Spring Boot 프로젝트에서 @ModelAttribute를 통해 요청 데이터를 받으면서, Kotlin의 데이터 클래스를 활용해 커스텀 유효성 검사 어노테이션을 적용할 때 발생했던 문제와 이를 해결한 과정을 정리한다.
문제 상황
다음과 같이 @ModelAttribute로 선언한 데이터 클래스 PayRequest가 있다. userId 필드에는 커스텀 유효성 검사 어노테이션이 적용되어 있다.
PayRequest 클래스
data class PayRequest(
val amount: Double, // 결제 금액
@CheckUserId
val userId: Long, // 사용자 ID
) {
init {
require(amount > 0) { "금액이 유효하지 않습니다." }
require(userId >= 0) { "사용자 ID는 음수일 수 없습니다." }
}
}
커스텀 어노테이션 @CheckUserId는 특정 사용자 ID가 존재하는 확인하기 위해 다음과 같이 정의되어 있다.
@CheckUserId 어노테이션 (예시)
@Retention(AnnotationRetention.RUNTIME)
@Target(
AnnotationTarget.TYPE,
AnnotationTarget.FIELD,
AnnotationTarget.VALUE_PARAMETER,
)
@Constraint(validatedBy = [CheckUserIdValidator::class])
annotation class CheckUserId(
val message: String = "해당 사용자가 존재하지 않습니다.",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = [],
)
@Component
class CheckUserIdValidator(
private val userDQryBus: IUserDomainQueryBus
) : ConstraintValidator<CheckUserId, Long> {
override fun isValid(value: Long, context: ConstraintValidatorContext?): Boolean {
return userDQryBus.existById(value)
}
}
컨트롤러에서는 다음과 같이 @Valid를 붙여 데이터를 검증하고자 했다.
컨트롤러 코드
@PostMapping("/pay")
fun processPayment(@Valid @ModelAttribute payRequest: PayRequest) {
//
}
테스트를 진행했을 때, @CheckUserId 와 같은 커스텀 유효성 검사 어노테이션이 작동하지 않았다.
문제 원인
Kotlin의 어노테이션 적용 위치와 Spring의 유효성 검사 처리 방식의 차이 때문이다. Kotlin에서 어노테이션을 val userId: Long와 같이 생성자의 필드에 선언하면, 기본적으로 생성자의 파라미터에 적용된다.
즉, Kotlin에서 data class나 일반 클래스의 주 생성자에 선언된 val 또는 var 프로퍼티에 어노테이션을 붙이면, 이 어노테이션은 해당 프로퍼티의 필드가 아니라 생성자의 파라미터에 적용된다.
이를 디컴파일된 바이트코드로 확인해보면 다음과 같다.
public final class PayRequest {
private final double amount;
private final long userId;
public PayRequest(double amount, @CheckUserId long userId) {
this.amount = amount;
this.userId= userId;
}
}
여기서 @CheckUserId 는 생성자의 파라미터에만 적용되어 있으며, Spring의 유효성 검사(@Valid)는 필드 또는 getter에 적용된 어노테이션만을 인식한다. 따라서, 커스텀 유효성 검사 어노테이션이 작동하지 않은 것이다.
해결 방법
1. @field를 사용해 어노테이션을 필드에 적용
data class PayRequest(
val amount: Double,
@field:CheckUserId
val userId: Long
)
2. Kotlin에서 어노테이션이 필드에 적용되도록 명시적으로 @field 키워드를 붙인다.
수정 후 디컴파일된 바이트코드를 확인하면 다음과 같다.
public final class PayRequest {
private final double amount;
@CheckUserId
private final long userId;
public final double getAmount() {
return this.amount;
}
public final long getUserId() {
return this.userId;
}
public PayRequest(double amount, long userId) {
this.amount = amount;
this.userId= userId;
}
위와 같이, @CheckUserId 어노테이션이 userId필드에 잘 적용된 것을 확인할 수 있다. 이는 의도한 대로, 어노테이션이 필드에 부착되어 Spring에서 유효성 검사를 정상적으로 처리할 수 있게 된 것을 의미한다.
'Kotlin' 카테고리의 다른 글
유효성 검사 리팩토링 : Kotlin when 구문으로 중복 줄이기 (1) | 2024.12.28 |
---|---|
코틀린의 스코프 함수(scope functions) (1) | 2024.10.26 |