웹개발 - Back 관련/Spring-Kotlin

[Kotlin Spring] Class 프로퍼티의 @NumberForrmat , @DateTimeFormat Annotation이 인식이 안되는 문제

정상호소인 2024. 6. 29. 15:38

Topic =  Kopring 환경에서,객체의 프로퍼티의 @NumberForamt 등의 Spring Annotation이 적용되지 않는 문제 ➡️ use-site target 지정하여 해결.

 


 

문제 코드, 해결 코드

 

🟥 문제 코드

data class FormatterForm (

    @NumberFormat(pattern = "#,###")
    val formNumber : Double,

    @DateTimeFormat(iso = DateTimeFormat.ISO.NONE, pattern = "yyyy,MM,dd HH:mm:ss:SSS")
    val date : LocalDateTime
)

 

 

✅ 해결 코드

data class FormatterForm (

    @field: NumberFormat(pattern = "#,###")
    val formNumber : Double,

    @field: DateTimeFormat(iso = DateTimeFormat.ISO.NONE, pattern = "yyyy,MM,dd HH:mm:ss:SSS")
    val date : LocalDateTime
)
  • Annotation 내부 맨 앞에 field : 속성을 추가하여 해결

 

 

 

 

 ' @filed :  ' 를 사용해야 하는 이유?

 

먼저 객체, 멤버변수(프로퍼티), 필드, backing field를 구분해보자.

 

Java 와는 달리, Kotlin의 객체는 컴파일 시 아래와 같이 백킹 필드와 프로퍼티로 나뉜다.

class User( name : String )
class User {
    private var _name: String = "Hello"

    var name: String
        get() = _name
        set(value) {
            _name = value
	}
}

 

객체 - TestUser

필드 - name 속성과 getter setter

backing field - name 프로퍼티의 실제 값

 

 

 

Spring - Field Annotation

Spring @Format Annotation 등 Field Annotation 의 작동 시점은, HTTP 요청을 처리할 때  ( Format의 경우 해당 객체를 View Template의 html 파일에 랜더링할 때 ) Formatter가 호출되어 등록된 pattern에 따라 문자열을 출력하게 된다. 


즉, Controller에서 @~~~Format(pattern = "~") 프로퍼티를 가진 객체를 생성할 때 파라미터가, Formatter를 통해 변환되는 것이 아닌, view template에서 Model에 담긴 객체의 필드에 바인딩할 때 Formatter를 작동시키는 것이다.

 

✅ 정리하자면 spring의 Field Annotation은 객체를 생성할 때 getter, setter 를 조작하는 것이 아닌, 객체의 name = "철수", age="5" 와 같이 진짜 name, age 속성의 값이 담긴 backing field 에 바인딩하는 것이기 때문에 필드에 직접적으로 Annotation 을 지정해주어야 한다는 것이다. Kotlin 코드의 경우 class 의 생성자에 Annotation이 위치해 있기 때문에 해당 프로퍼티의 field 로 ues-site target을 지정해주어야 한다.

 

예시 - Controller에서 Model에 담긴 객체를 확인할 때 로그로 확인 해보아도, 여전히 Format되지 않은 상태이다. FormatterForm은 본문 상단의 코드이며 문제 코드, 해결 코드 둘 다 동일하게 Model에 담길 때 까지는 Format되지 않은 상태이다.

private val logger = KotlinLogging.logger {  }

@Controller
class FormatterController {

    @GetMapping("/formatter/edit")
    fun formatterForm(model: Model): String {
        val formatterForm = FormatterForm(10000.0, LocalDateTime.now())

        model.addAttribute("formatterForm", formatterForm)
        logger.info { model.getAttribute("formatterForm") }
        return "formatter/form"
    }
}

 

- Log

 

 

@field 를 지정했을 때, 안했을 때 작동방식

 

use-site target 미적용

class User (private var _name: String = "Hello") {
    @get:NumberFormat(pattern ="#,###")
    var name: String
        get() = _name
        set(value) {
            _name = value
	}
}

➡️ @NumberFormat 주석이 backing field 에 영향을 줄 수 없으므로 변동이 없다.

 

use-site target 적용

class User (
    @field:NumberFormat(pattern ="#,###")
    private var _name: String = "Hello"
    ) {
    var name: String
        get() = _name
        set(value) {
            _name = value
		}
}

➡️ Spring Annotation이 name 속성에 직접 접근하게 되어 정상 작동된다.

 

 


 

작동 순서에 대해서는 AbstractNumberFormatter에 브레이크 포인트를 찍고 디버그 모드로 확인해보자.

 

 

 

 

 

 

 

 

 


[Reference]

 

kotlin data class + bean validation jsr 303

I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project. Given the following data class declarartion : @Entity data class User( @Id @GeneratedValue(strat...

stackoverflow.com

 

 

Spring Field Formatting :: Spring Framework

As discussed in the previous section, core.convert is a general-purpose type conversion system. It provides a unified ConversionService API as well as a strongly typed Converter SPI for implementing conversion logic from one type to another. A Spring conta

docs.spring.io