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]