본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[Kotlin Tutorial] Annotation 과 Reflection #2

by 돼지왕 왕돼지 2017. 9. 12.
반응형

[Kotlin Tutorial] Annotation 과 Reflection #2



참조 : Kotlin in action

.kotlin extension property, ::, @get:rule, annotation, annotation argument, annotation class, annotation instance collection, Annotations, Argument, array, body, bytecode, Call, call 함수, callable, callby, character token, CLASS, class reference, compiler-generated types, constructor, constructors, createInstance, Customizing serialization with annotations, default parameter, default value, dependency, deserializer, enum, expression, file, function, generic class, get method, getter, IllegalArgumentException, Implementing object serialization using reflection, Interface, Introspect, introspecting Kotlin objects at runtime, Invoke, Java, java.lang.reflect, javaclass property, javaclass.kotlin, JSON parsing and object deserialization, KAnnoatedElement, KAnnotatedElement, kcallable, kclass, kfunction, kfunction0, KFunction1, KFunction2, kmutableproperty0, kmutableproperty1, Kotlin, kotlin annotation, kotlin basics, kotlin java, kotlin reflection api, kotlin tutorial, kotlin-reflect.jar, kotlin.reflect, KParameter, kproperty, kproperty interface, KProperty0, KProperty1, lexer, lexical analyzer, list, member property, memberProperties, Members, meta-annotation, Method, nestedClasses, non-extension property, nullable type, obj.javaclass.kotlin, object, object instance, object.getclass, objectInstance, out r, pacakge, Param, parser, plain token list, primary constructor, primitive value, property, property call, qualifiedName, receiver, Reflection, reflection api, retention mode, Runtime, runtime lib, runtime retention, Set, set method, Setter, simpleName, singleton object, Size, string, string token, superinterface, syntax analyzer, synthetic compiler-generated types, Target, target ragne, The Kotlin reflection API, type safe, use-site target annotation, Val, value token, vararg, [Kotlin Tutorial] Annotation 과 Reflection #2, 코틀린, 코틀린 기초, 코틀린 기초 강좌


10.2. Reflection: introspecting Kotlin objects at runtime


-

Kotlin 에서의 reflection 은 java.lang.reflect package 의 API 들과 kotlin.reflect package 의 API 들을 사용한다.

Kotlin 의 reflection 은 Java 에 없는 nullable type 이나 properties 들의 접근을 가능하게 한다.



-

runtime lib 의 size 를 줄이기 위해, kotlin 의 reflection API 는 kotlin-reflect.jar 라는 별개의 jar 로 pakcage 되어 있다.

그리고 이것은 새 project 에 기본 dependency 로 들어가지 않는다.

그래서 Kotlin reflection 을 사용하려면 꼭 dependency 를 추가해야 한다.




10.2.1. The Kotlin reflection API: KClass, KCallable, KFunction, and KProperty


-

KClass 는 Java 의 java.lang.Class 에 해당하는 녀석이다.

KClass 는 MyClass::class 의 형태로 얻을 수 있다.



-

Runtime 에서 얻을 경우에는 javaClass property 를 통해 Java class 를 얻는다.

이는 Object.getClass() 와 동일하다.

.kotlin extension property 를 통해 Java 에서 Kotlin reflection API 로 옮겨올 수 있다.

class Person(val name: String, val age: Int)


val person = Person(“Alice”, 29)

val kClass = person.javaClass.kotlin

println(kClass.simpleName) // Person

kClass.memberProperties.forEach{ println(it.name) } // age 와 name 출력, non-extension property 들만 가져온다.



-

interface KClass<T : Any>{

    val simpleName: String?

    val qualifiedName: String?

    val members: Collection<KCallable<*>> 

    val constructors: Collection<KFunction<T>>

    val nestedClasses: Collection<KClass<*>>

    ...

}


memberProperties 는 extension 으로 정의되어 있다.



-

KCallable 은 function, properties 의 super interface, call 함수를 가지고 있다

interface KCallable<out R> {

    fun call(vararg args: Any?): R

}


fun foo(x:Int) = println(x)

val kFunction = ::foo // KFunction1<Int, Unit> 타입, 1 은 param 이 하나라는 의미

kFunction.call(42) // argument mismatch case 에는 IllegalArgumentException 이 발생



-

import kotlin.reflect.KFunction2


fun sum(x: Int, y: Int) = x+ y


val kFunction : KFunction2<Int, Int, Int> = ::sum

println(kFunction.invoke(1,2) + kFunction(3, 4))

kFunction(1) // error


KFunction 의 경우 invoke 로 수행한다. ( KFunction 의 interface 에는 invoke 가 있으며, 갯수와 type 이 명시 )

물론 call 로도 가능하다. 그러나 type safe 하지 않다.



-

KFunction2 같은 것은 어디에 정의되어 있을까?

이런 녀석들은 synthetic compiler-generated types 라고 부른다.

compiler 가 필요할 때 정의해서 만드는 녀석이라는 의미로, 이 코드는 package 에서 찾을 수 없다.

이 접근 방법으로 kotlin-runtime.jar 의 사이즈를 줄일 수 있고, 갯수 제한에 대한 문제로 뛰어 넘을 수 있다.



-

KProperty 에도 call 함수를 호출할 수 있다. 이는 getter 를 호출한다.

그러나 getter 를 얻어와서 호출하는 것이 당연히 더 좋다.


top-level property 는 KProperty0 interface 를 상속하며, 이 녀석은 get method 를 가지고 있다.

var counter = 0

val kProperty = ::counter

kProperty.setter.call(21)

println(kProperty.get()) // 21



-

member property 는 KProperty1 이며 한개의 argument 를 갖는 get method 를 가지고 있다.

첫번째 param 에는 접근하려는 object instance 를 전달해야 한다.

class Person(val name:String, val age:Int)


val person = Person(“Alice”, 29)

val memberProperty = Person::age

println(memberProperty.get(person)) // 29


또한 KProperty1 은 generic class 이다.

위의 예에서 memberProperty 는 KProperty<Person, Int> 이다.



-

KClass, KCallable, KParameter 는 모두 KAnnotatedElement interface 를 구현한다.

.kotlin extension property, ::, @get:rule, annotation, annotation argument, annotation class, annotation instance collection, Annotations, Argument, array, body, bytecode, Call, call 함수, callable, callby, character token, CLASS, class reference, compiler-generated types, constructor, constructors, createInstance, Customizing serialization with annotations, default parameter, default value, dependency, deserializer, enum, expression, file, function, generic class, get method, getter, IllegalArgumentException, Implementing object serialization using reflection, Interface, Introspect, introspecting Kotlin objects at runtime, Invoke, Java, java.lang.reflect, javaclass property, javaclass.kotlin, JSON parsing and object deserialization, KAnnoatedElement, KAnnotatedElement, kcallable, kclass, kfunction, kfunction0, KFunction1, KFunction2, kmutableproperty0, kmutableproperty1, Kotlin, kotlin annotation, kotlin basics, kotlin java, kotlin reflection api, kotlin tutorial, kotlin-reflect.jar, kotlin.reflect, KParameter, kproperty, kproperty interface, KProperty0, KProperty1, lexer, lexical analyzer, list, member property, memberProperties, Members, meta-annotation, Method, nestedClasses, non-extension property, nullable type, obj.javaclass.kotlin, object, object instance, object.getclass, objectInstance, out r, pacakge, Param, parser, plain token list, primary constructor, primitive value, property, property call, qualifiedName, receiver, Reflection, reflection api, retention mode, Runtime, runtime lib, runtime retention, Set, set method, Setter, simpleName, singleton object, Size, string, string token, superinterface, syntax analyzer, synthetic compiler-generated types, Target, target ragne, The Kotlin reflection API, type safe, use-site target annotation, Val, value token, vararg, [Kotlin Tutorial] Annotation 과 Reflection #2, 코틀린, 코틀린 기초, 코틀린 기초 강좌




10.2.2. Implementing object serialization using reflection


-

private fun StringBuilder.serializeObject(obj: Any){

    val kClass = obj.javaClass.kotlin

    val properties = kClass.memberProperties


    properties.jointToStringBuilder(this, prefix = “{“, postfix = “}”) { prop -> // KProperty1<Any, *> type

        serializeString(prop.name) // JSON format 으로 escape 한다

        append(“: “)

        serializePropertyValue(prop.get(obj)) // primitive, string, collection, nested object 로 serialize

}


fun serialize(obj: Any): String = buildString { serializeObject(obj) }


buildString 은 StringBuilder 를 만들고 lambda 를 이용해 fill 하도록 한다.




10.2.3. Customizing serialization with annotations


-

KAnnotatedElement interface 는 annotations 라는 property 를 가지고 있다.

이는 annotation instance collection 이다. (  runtime retention 인 )



-

inline fun <reified T> KAnnotatedElement.findAnnotation(): T? = annotations.filterIsInstance<T>().firstOrNull()


val properties = kClass.memberProperties.filter{ it.findAnnotation<JSONExclude>() == null }


val jsonNameAnn = prop.findAnnotation<JsonName>()

val propName = jsonNameAnn?.name ?: prop.name



-

annotation class CustomSerializer{

    val serializerClass: KClass<out ValueSerializer<*>>

}


data class Person(val name: String, @CustomSerializer(DateSerializer::class) val birthDate: Date)


fun KProperty<*>.getSerializer(): ValueSerializer<Any?>?{

    val customSerializerAnn = findAnnotation<CustomSerializer>() ?: return null

    val serializerClass = customSerializerAnn.serializerClass


    val valueSerializer = serialzierClass.objectInstance?: serializerClass.createInstance()

    @Suppress(“UNCHECKED_CAST”)

    return valueSerializer as ValueSerializer<Any?>

}


class 와 singleton object 는 동일하게 KClass 이다.

objectInstance 로는 singleton object 가 접근된다.

singletone 이 아닐 경우에는 createInstance 로 instance 를 만든다.



-

private fun StringBuilder.serializeProperty(prop: KProperty1<Any, *>, obj: Any){

    val name = prop.findAnnotation<JsonName>()?.name ?: prop.name

    serializeString(name)

    append(“: “)


    val value = prop.get(obj)

    val jsonValue = prop.getSerializer()?.toJsonValue(value) ?: value

    serializePropertyValue(jsonValue)

}




10.2.4. JSON parsing and object deserialization


-

inline fun <reified T: Any> deserialize(json: String): T


data class Author(val name:String)

data class Book(val title:String, val author:Author)


val json = “””{“title”: “Catch-22”, “author”: {“name”:”J.Heller”}}”””

val book = deserialize<Book>(json)

println(book)



-

JKid 의 deserializer 는 lexical analyzer(lexer), syntax analyzer, 그리고 parser 로 구성되어 있다.


lexical analysis 는 string 을 token 으로 나눈다.

두가지 형태의 token 이 있는데 character token ( comma, colon, braces, brackets ) 과 value token ( string, number, boolean, null ) 이 있다.


parser 는 plain token list 를 structured form 으로 변경하는 것을 이야기한다.

JKid 는 JSON 이 structured form 이다.


.kotlin extension property, ::, @get:rule, annotation, annotation argument, annotation class, annotation instance collection, Annotations, Argument, array, body, bytecode, Call, call 함수, callable, callby, character token, CLASS, class reference, compiler-generated types, constructor, constructors, createInstance, Customizing serialization with annotations, default parameter, default value, dependency, deserializer, enum, expression, file, function, generic class, get method, getter, IllegalArgumentException, Implementing object serialization using reflection, Interface, Introspect, introspecting Kotlin objects at runtime, Invoke, Java, java.lang.reflect, javaclass property, javaclass.kotlin, JSON parsing and object deserialization, KAnnoatedElement, KAnnotatedElement, kcallable, kclass, kfunction, kfunction0, KFunction1, KFunction2, kmutableproperty0, kmutableproperty1, Kotlin, kotlin annotation, kotlin basics, kotlin java, kotlin reflection api, kotlin tutorial, kotlin-reflect.jar, kotlin.reflect, KParameter, kproperty, kproperty interface, KProperty0, KProperty1, lexer, lexical analyzer, list, member property, memberProperties, Members, meta-annotation, Method, nestedClasses, non-extension property, nullable type, obj.javaclass.kotlin, object, object instance, object.getclass, objectInstance, out r, pacakge, Param, parser, plain token list, primary constructor, primitive value, property, property call, qualifiedName, receiver, Reflection, reflection api, retention mode, Runtime, runtime lib, runtime retention, Set, set method, Setter, simpleName, singleton object, Size, string, string token, superinterface, syntax analyzer, synthetic compiler-generated types, Target, target ragne, The Kotlin reflection API, type safe, use-site target annotation, Val, value token, vararg, [Kotlin Tutorial] Annotation 과 Reflection #2, 코틀린, 코틀린 기초, 코틀린 기초 강좌




10.2.5. Final deserialization step: callBy() and creating objects using reflection


-

KCallable.call 은 function 이나 constructor 를 호출할 수 있고, argument 들을 list 형태로 받을 수 있다.

많은 경우에 잘 작동하지만 제약사항이 있다.

default parameter value 를 제공하지 않는다.

interface KCallable<out R> {

    fun callBy(args: Map<KParameter, Any?>): R

    ....

}


callBy 는 전달하는 Map 에 빠진 param 이 있다면 default value 가 자동으로 사용된다. 즉 default value 를 잘 지원한다.

Map 으로 전달하니, named-argument 를 사용하는 것처럼 순서에도 영향을 받지 않는다.

Primary constructor 를 호출하는 데에도 사용될 수 있다.




-

기타 여러가지 내용들이 있었으나... 저도 잘 모르겠으니.. 따로 공부해보세요… ㅎㅎ





10.3. Summary

-

Kotlin 의 annotation 적용 syntax 는 Java 와 거의 같다.



-

Kotlin 은 Java 보다 더 넓은 target range 를 제공한다.

file, expression, property 에 추가 적용 가능하다.



-

annotation argument 는 primitive value, string, enum, class reference, 다른 annotation class, 그리고 앞선 애들의 array 가 가능하다.



-

@get:Rule 과 같은 use-site target annotation 지정은 Kotlin 이 single line 을 bytecode 에서는 여러 라인으로 만드는 경우에 선택적 적용을 할 수 있게 한다.



-

모든 param 이 val 로 마킹되며, body 가 없는 primary constructor 를 가진 annotation class 정의가 가능하다.

( primary constructor 는 없어도 된다 )



-

Meta-annotation 은 target, retention mode, 그리고 다른 annotation 의 attribute 를 명시할 때 사용된다.



-

Reflection API 는 object 의 method 와 property 들을 runtime 에 동적으로 접근할 수 있게 한다.

KClass, KFunction 등의 다른 종류의 정의 interface 를 포함한다.



-

KClass instance 를 얻기 위해서는 ClassName::class 를 하면 된다.

정적으로 알 수 없는 경우는 obj.javaClass.kotlin 으로 KClass 를 얻을 수 있다.



-

KFunction 과 KProperty interface 는 KCallable 을 상속했다.

그들은 generic “call” method 를 가지고 있다.



-

KCallable.callBy 는 default parameter value 를 가진 method 를 호출할 때 쓰일 수 있다.



-

KFunction0, KFunction1 등은 invoke 를 통해 호출 될 수 있다.



-

KProperty0, KProperty1 등은 receiver 와 여러개의 갯수의 param 을 받을 수 있다.

그리고 get method 를 제공한다.

KMutableProperty0 과 KMutableProperty1 은 set method 도 가지고 있다.





반응형

댓글