(Kotlin) 한 번만 초기화 되는 필드 - Delegates.initOnlyOnce
Delegates.initOnlyOnce
var
이면서 한 번만 초기화 될 수 있는 필드가 필요한 경우가 있다. (하단 사례 참조)
이런 패턴 적용하는데 적합한건 Delegate인데, standard Delegates 중에 제공되는건 없다.
최초 null로 초기화 하고, 현재 상태가 null이 아니면 다른 값으로 초기화는 불가능. 한 Delegate를 직접 작성해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private class InitOnlyOnce<T: Any>(): ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
check(this.value == null) {
"Property ${property.name} can't be initialized more than once."
}
this.value = value
}
}
fun <T : Any> Delegates.initOnlyOnce(): ReadWriteProperty<Any?, T> = InitOnlyOnce()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class InitOnlyOnceTest {
@Test
fun testInitOnlyOnce() {
val a = A()
assertThrows<IllegalStateException> { a.prop }
a.prop = 1
assert(a.prop == 1)
assertThrows<IllegalStateException> { a.prop = 2 }
assert(a.prop == 1)
}
class A {
var prop by Delegates.initOnlyOnce<Int>()
}
}
사례
.properties를 담고 있는 Spring 영역 바깥의 Object
.properties
나 yml
에 있는 환경에 따라 달라지는 속성(프로퍼티)들을 가져올 때 (e.g., url)
DI로 가져오기 때문에, Bean이 아닌 DTO 같은 클래스에서는 url 속성 등에 접근 할 수 없다.
즉 이런 데이터는 반드시 Bean으로부터 넘겨 받아야 하는데, 이 점이 생각을 표현하는데 허들이 되는 경우가 종종 있다.
=> DTO 같은 Spring 영역 바깥에 존재하는 클래스에서도 자유롭게 참조 할 수 있는 Singleton 객체가 필요하다.
Spring 영역 바깥에서 singleton 만드려면 컴파일 타임에 생성되는 Object
를 이용하게 되는데,
이런 프로퍼티들은 애플리케이션 시작 시점에 적절한 프로파일 별 .yml
을 읽어와야 알 수 있으므로 컴파일 타임에 세팅되는 정적 데이터는 아니다.
따라서 Object.field
는 var
처리 할 수 밖에 없다.
var
이므로 런타임에 언제든 변경 될 수 있다는 위험을 안고 있어, 이를 방지할 안전장치가 필요하다. => initOnlyOnce
Spring Batch 잡 파라미터
잡을 실행하는데 필요한 파라미터를 모아 VO로 관리하게 되는데
잡 파라미터는 보통 Job.afterStep
에서 런타임에 처리하게 된다.
따라서 잡 파라미터 필드도 var
처리 할 수 밖에 없다. => 두번 초기화 되지 않게 initOnlyOnce