티스토리 뷰
728x90
20강. 코틀린의 어노테이션
어노테이션이란?
어노테이션을 붙여 개발자에게 의견을 알리거나 특별한 일이 일어나도록 할 수 있음
어노테이션 만드는 방법
annotation class Shape
annotation class Shape(
val test: String,
val number: Int,
val clazz: KClass<*>
)
KClass
코드로 작성한 클래스를 표현하는 클래스
val kClass: KClass<Annotation> = Annotation::class
@Retention
우리의 어노테이션이 저장되고 유지되는 방식을 제어
@Retention(AnnotationRetention.RUNTIME)
annotation class Shape
Retention
의 종류
- SOURCE: 어노테이션이 컴파일때만 존재
- BINARY: 어노테이션이 런타임때도 있지만 리플렉션을 쓸수 없음
- RUNTIME(기본값): 어노테이션이 런타임때 존재하고 리플렉션을 쓸수 있음
@Target
어노테이션을 붙이는 위치를 설정
@Target(AnnotationTarget.CLASS)
annotation class Shape
어노테이션 사용 방법
annotation class Shape(
)
@Shape
class Hello { }
@[Shape Shape1]
class Hello { }
annotation class Shape(
val text: String,
val number: Int,
)
@Shape("text", 10)
class Hello { }
annotation class Shape(
val text: Array<String>
)
@Shape(["text1", "text2"])
class Hello { }
위치가 애매한경우
class Hello(@Shape val name: String)
위 @Shape
는 여러가지 위치로 해석 될수 있음
- 생성자 파라미터
name
name
프로퍼티name
필드name
의 getter
위로 인해 아래와같이 정확한 위치를 설정해줘야함
class Hello(@get:Shape val name: String)
@Repeatable
한 언어 요소에 여러번 붙일수 있는 어노테이션
@Repeatable
annotation class Shape(
val text: Array<String>
)
@Shape(["text1", "text2"])
@Shape(["text3", "text4"])
class Hello { }
21강. 코틀린의 리플렉션
리플렉션을 통한 예제
- 함수
executeAll(obj: Any)
를 만든다 obj
가@Executable
어노테이션을 갖고있으면obj
에서 파라미터가 없고 반환타입이Unit
인 함수를 모두 실행
의존성 추가
- build.gradle.kts
runtimeOnly("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
리플렉션 API
KClass
- KClass: 코틀린에서의 리플렉션 클래스
- Class: 자바에서의 리플렉션 클래스
annotation class Reflection
fun main() {
val kClass = Reflection::class
val instance = Reflection()
val kClass2 = instance::class
val kClass3 = Class.forName("com.reflection.Reflection").kotlin
kClass.java // Class
kClass.java.kotlin // KClass
}
cast()
class GoldFish()
fun castToGoldfish(obj: Any) {
obj as GoldFish
GoldFish::class.cast(obj)
}
KType
타입을 표현하는 클래스
val kType: KType = GoldFish::class.createType()
KParameter
코틀린의 파라미터를 표현
KTypeParameter
코틀린의 타입 파라미터를 표현
KCallable
호출될수 있는 언어 요소 표현
KFuntion
코틀린에 있는 함수 표현
KProperty
코틀린에 있는 프로퍼티 표현
간단한 리플렉션 사용 예제
class GoldFish(val name: String) {
fun print() {
println("${name} 금붕어!")
}
}
fun main() {
val goldfish = GoldFish("금붕")
goldfish::class.members.first { it.name == "print" }.call(goldfish)
}
예제 구현
annotation class Executable
@Executable
class Reflection {
fun a() {
println("a 함수입니다.")
}
fun b(): Int {
println("b 함수입니다.")
return 1
}
}
fun executeAll(obj: Any) {
val kClass = obj::class
if (!kClass.annotations.contains(Executable())) {
return
}
val callableFunc = kClass.members.filterIsInstance<KFunction<*>>()
.filter { it.returnType == typeOf<Unit>() }
.filter { it.parameters.size == 1 && it.parameters[0].type == typeOf<Reflection>() }
callableFunc.forEach { it.call(kClass.constructors.first().call()) }
}
fun main() {
executeAll(Reflection())
// a 함수입니다.
}
22강. 리플렉션 활용 - 나만의 DI 컨테이너 만들기
DI 컨테이너
- 클래스 정보 관리
- 등록된 클래스 인스턴스화
- 등록된 클래스끼리 연결
1단계
object ContainerV1 {
private val registeredClasses = mutableSetOf<KClass<*>>()
fun register(clazz: KClass<*>) {
this.registeredClasses.add(clazz)
}
fun <T: Any> getInstance(type: KClass<T>): T {
return registeredClasses.firstOrNull { it == type }
?.let { it.constructors.first().call() as T }
?: throw IllegalArgumentException("해당 인스턴스를 찾을 수 없습니다.")
}
}
fun main() {
ContainerV1.register(AService::class)
ContainerV1.getInstance(AService::class).print()
}
class AService {
fun print() {
println("AService 입니다.")
}
}
2단계
object ContainerV2 {
private val registeredClasses = mutableSetOf<KClass<*>>()
private val cachedInstances = mutableMapOf<KClass<*>, Any>()
fun register(clazz: KClass<*>) {
this.registeredClasses.add(clazz)
}
fun <T: Any> getInstance(type: KClass<T>): T {
if (type in cachedInstances) {
return type.cast(this.cachedInstances[type])
}
val instance = registeredClasses.firstOrNull { it == type }
?.let { instantiate(it) as T }
?: throw IllegalArgumentException("해당 인스턴스를 찾을 수 없습니다.")
this.cachedInstances[type] = instance
return instance
}
private fun <T: Any> instantiate(clazz: KClass<T>): T {
val constructor = findUsableConstructor(clazz)
val params = constructor.parameters
.map { param -> getInstance(param.type.classifier as KClass<*>) }
.toTypedArray()
return constructor.call(*params)
}
// clazz의 생성자 중 사용할수 있는 생성자를 사용
// 생성자에 넣어야할 타입들이 모두 등록된 경우를 의미
private fun <T: Any> findUsableConstructor(clazz: KClass<T>): KFunction<T> {
return clazz.constructors.firstOrNull { constructor -> constructor.parameters.isAllRegistered }
?: throw IllegalArgumentException("사용할 수 있는 생성자가 존재하지 않습니다.")
}
private val List<KParameter>.isAllRegistered: Boolean
get() = this.all { it.type.classifier in registeredClasses }
}
fun main() {
ContainerV2.register(AService::class)
ContainerV2.register(BService::class)
ContainerV2.getInstance(BService::class).print()
}
class AService {
fun print() {
println("AService 입니다.")
}
}
class BService(
private val aService: AService
) {
fun print() {
println("BService 입니다.")
this.aService.pri
3단계
의존성 추가
implementation("org.reflections:reflections:0.10.2")
object ContainerV2 {
private val registeredClasses = mutableSetOf<KClass<*>>()
private val cachedInstances = mutableMapOf<KClass<*>, Any>()
fun register(clazz: KClass<*>) {
this.registeredClasses.add(clazz)
}
fun <T: Any> getInstance(type: KClass<T>): T {
if (type in cachedInstances) {
return type.cast(this.cachedInstances[type])
}
val instance = registeredClasses.firstOrNull { it == type }
?.let { instantiate(it) as T }
?: throw IllegalArgumentException("해당 인스턴스를 찾을 수 없습니다.")
this.cachedInstances[type] = instance
return instance
}
private fun <T: Any> instantiate(clazz: KClass<T>): T {
val constructor = findUsableConstructor(clazz)
val params = constructor.parameters
.map { param -> getInstance(param.type.classifier as KClass<*>) }
.toTypedArray()
return constructor.call(*params)
}
// clazz의 생성자 중 사용할수 있는 생성자를 사용
// 생성자에 넣어야할 타입들이 모두 등록된 경우를 의미
private fun <T: Any> findUsableConstructor(clazz: KClass<T>): KFunction<T> {
return clazz.constructors.firstOrNull { constructor -> constructor.parameters.isAllRegistered }
?: throw IllegalArgumentException("사용할 수 있는 생성자가 존재하지 않습니다.")
}
private val List<KParameter>.isAllRegistered: Boolean
get() = this.all { it.type.classifier in registeredClasses }
}
fun start(clazz: KClass<*>) {
val reflections = Reflections(clazz.packageName)
val jClasses = reflections.getTypesAnnotatedWith(MyClass::class.java)
jClasses.forEach { ContainerV2.register(it.kotlin) }
}
private val KClass<*>.packageName: String
get() {
val qualifiedName = this.qualifiedName ?: throw IllegalArgumentException("익명 객체 입니다.")
val hierarchy = qualifiedName.split(".")
return hierarchy.subList(0, hierar
23강. 리플렉션 활용 - 타입 안전 이종 컨테이너와 슈퍼 타입 토큰
클래스를 제너릭으로 만들지 않고도 타입 안전하게 동물을 가져올 수 있는 방법이 있음
예제
- 아래 예제에서
KClass<*>
가 타입토큰 역할을 함
class TypeSafeCage {
private val animals: MutableMap<KClass<*>, Animal> = mutableMapOf()
fun <T: Animal> getOne(type: KClass<T>): T {
return type.cast(this.animals[type])
}
fun <T: Animal> putOne(type: KClass<T>, animal: T) {
this.animals[type] = animal
}
inline fun <reified T: Animal> getOne(): T {
return this.getOne(T::class)
}
inline fun <reified T: Animal> putOne(animal: T) {
this.putOne(T::class, animal)
}
}
fun main() {
val typeSafeCage = TypeSafeCage()
typeSafeCage.putOne(Carp::class, Carp("잉어"))
typeSafeCage.putOne(Carp("잉어"))
val carp = typeSafeCage.getOne(Carp::class)
val carp2: Carp = typeSafeCage.getOne()
}
위와같은 패턴을 **타입 안전 이종 컨테이너** 패턴이라고 함
타입 안전 이종 컨테이너의 취약점
- 아래와같은 현상은 제네릭 타입 소거가 원인
val cage = TypeSafeCage()
cage.putOne(listOf(GoldFist("금붕어1"), GoldFist("금붕어1")))
val carps: List = cage.getOne() // 금붕어 리스트를 반환받음
슈퍼 타입 토큰
제네릭 타입 정보를 리플렉션으로 알아내기
`List<T>`를 저장하면 `List`와 `T`를 기억함
class SuperTypeSafeCage {
private val animals: MutableMap<SuperTypeToken<*>, Any> = mutableMapOf()
fun <T: Any> getOne(token: SuperTypeToken<T>): T {
return this.animals[token] as T
}
fun <T: Any> putOne(token: SuperTypeToken<T>, animal: T) {
this.animals[token] = animal
}
}
// SuperTypeToken을 구현한 클래스가 인스턴스화 되자마자
// T정보를 내부 변수에 저장함
abstract class SuperTypeToken<T> {
val type: KType = this::class.supertypes[0].arguments[0].type!!
override fun equals(other: Any?): Boolean {
if (this === other) return true
other as SuperTypeToken<*>
return type == other.type
}
override fun hashCode(): Int {
return type.hashCode()
}
}
fun main() {
val superTypeToken1 = object : SuperTypeToken<List<GoldFish>>() {}
val superTypeToken2 = object : SuperTypeToken<List<GoldFish>>() {}
val superTypeToken3 = object : SuperTypeToken<List<Carp>>() {}
println(superTypeToken1.equals(superTypeToken2)) // true
println(superTypeToken1.equals(superTypeToken3)) // false
val superTypeSafeCage = SuperTypeSafeCage()
superTypeSafeCage.putOne(superTypeToken1, listOf(GoldFish("금붕어1"), GoldFish("금붕어2")))
// val result1 = superTypeSafeCage.getOne(superTypeToken3) // error
val result2 = superTypeSafeCage.getOne(superTypeToken1)
println(result2)
}
위 내용은 인프런 코틀린 고급편강의를 시청하고 작성했습니다
인프런
728x90
'개발 언어 > 코틀린' 카테고리의 다른 글
kotlin - coroutine (3) 코루틴의 동작 원리 및 구조화된 동시성 (Structured Concurrency) (0) | 2024.09.05 |
---|---|
kotlin - coroutine (1) 코루틴의 기초 개념 (0) | 2024.09.05 |
인프런 - 코틀린 고급편 (4) 연산자 오버로딩 & DSL (0) | 2024.07.11 |
인프런 - 코틀린 고급편 (3) 함수형 프로그래밍 활용 (0) | 2024.07.04 |
인프런 - 코틀린 고급편 (2) 지연과 위임 (1) | 2024.07.01 |
댓글