2022. 2. 20. 17:55ㆍKotlin
지난 글(9장. 제네릭스(Generics)에 이어 Kotlin in Action 이라는 책을 보면서 매주 공부한 내용을 블로그에 기록한다.
이번 주는 10장 Annotaion 과 Reflection 에 대한 내용이다. 이번 챕터는 개념보다는 annotaion 과 reflection 이 많이 사용되는 json serializer 인 제이키드(jkid) 오픈소스 라이브러리를 분석하는 내용으로 이루어져 있기 때문에 기본 개념만 정리한다.
목차
Annotaion
Android Annotations is an annotation-driven framework that allows you to simplify the code in your applications and reduces the boilerplate of common patterns, such as setting click listeners, enforcing ui/background thread executions, etc.
- stackoverflow
애노테이션 예시 👇
// @StringRes: 파라미터가 R.string reference 를 포함하고 있는지 체크
abstract fun setTitle(@StringRes resId: Int)
// @VisibleForTesting: 테스트 코드 외에서 해당 함수를 호출하게 될 경우, lint 가 경고 메세지를 표시함
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun myMethod() {
...
}
애노테이션을 활용한 JSON 직렬화 제어
애노테이션을 사용하는 고전적인 예제로 객체 직렬화 제어를 들 수 있다.
직렬화(serialization)는 객체를 저장장치에 저장하거나 네트워크를 통해 전송하기 위해 텍스트나 이진 형식으로 변환하는 것이다.
반대 과정인 역직렬화(deserialization)는 텍스트나 이진 형식으로 저장된 데이터로부터 원래의 객체를 만들어낸다. 직렬화에 자주 쓰이는 형식에 JSON 이 있다.
JSON 직렬화를 위한 제이키드(jkid)라는 순수 코틀린 라이브러리가 있는데, 코드가 쉽기 때문에 소스코드를 모두 확인해볼 것.
JSON 에는 객체의 타입이 저장되지 않기 때문에 JSON 데이터로부터 인스턴스를 만들려면 타입 인자로 클래스를 명시해야 한다.
애노테이션을 활용해 객체를 직렬화하거나 역직렬 화하는 방법을 제어할 수 있다.
- @JsonExcluse : 직렬화/역직렬화 시 그 프로퍼티를 무시할 수 있음
- @JsonName : 프로퍼티를 표현하는 키/값 쌍의 키로 프로퍼티 이름 대신 애노테이션이 지정한 이름을 쓰게 할 수 있음
애노테이션 만들기
애노테이션 선언은 아래와 같다. 애노테이션 클래스는 오직 선언이나 식과 관련 있는 메타데이터의 구조를 정의하기 때문에 내부에 아무 코드도 들어있을 수 없다.
annotaion class JsonExclude
annotaion class JsonName(val name: String)
메타애노테이션 이란?
애노테이션 클래스에 적용할 수 있는 애노테이션을 메타애노테이션(meta-annotaion)이라고 부른다.
예시.
가장 흔하게 쓰이는 메타애노테이션은 @Target 이다. 애노테이션의 적용 가능 대상을 지정하기 위한 메타애노테이션이다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
public @interface Bogus {
...
}
정적인 데이터를 인자로 유지하는 애노테이션을 정의하는 방법을 살펴보았다. 하지만 어떤 클래스를 선언 메타데이터로 참조할 수 있는 기능이 필요할 때도 있다. 클래스 참조를 파라미터로 하는 애노테이션 클래스를 선언하면 그런 기능을 사용할 수 있다.
예시.
@DeserializeInterface(CompanyImpl::class)
annotaion class DeserializeInterface(val targetClass: KClass<out Any>)
KClass 는 java.lang.Class 타입과 같은 역할을 하는 코틀린 타입이다. 코틀린 클래스에 대한 참조를 저장할 때 KClass 타입을 사용한다.
Reflection
리플렉션이란?
실행 시점에 (동적으로) 객체의 프로퍼티와 메소드에 접근할 수 있게 해주는 방법이다. 보통 객체의 메소드나 프로퍼티에 접근할 때는 프로그램 소스코드 안에 구체적인 선언이 있는 메소드나 프로퍼티 이름을 사용하며, 컴파일러는 그런 이름이 실제로 가리키는 선언을 컴파일 시점에 찾아내서 해당하는 선언이 실제 존재함을 보장한다. 하지만 타입과 관계없이 객체를 다뤄야 하거나 객체가 제공하는 메소드나 프로퍼티 이름을 오직 실행 시점에만 알 수 있는 경우가 있다. JSON 직렬화 라이브러리가 그런 경우다. 직렬화 라이브러리는 어떤 객체든 JSON으로 변환할 수 있어야 하고, 실행 시점이 되기 전까지는 라이브러리가 직렬화할 프로퍼티나 클래스에 대한 정보를 알 수 없다. 이런 경우 리플렉션을 사용해야 한다.
코틀린에서 리플렉션을 사용하려면 두 가지 서로 다른 리플렉션 API 를 다뤄야 한다.
- java.lang.reflect 패키지를 통해 제공하는 표준 리플렉션
- 코틀린이 kotlin.reflect 패키지를 통해 제공하는 코틀린 리플렉션
- 자바에는 없는 프로퍼티나 널이 될 수 있는 타입과 같은 코틀린 고유 개념에 대한 리플렉션을 제공한다.
- 코틀린 클래스만 다룰 수 있는 것은 아니라는 점. 코틀린 리플렉션 API를 사용해도 다른 JVM 언어에서 생성한 바이트코드를 충분히 다룰 수 있다.
코틀린 리플렉션 API: KClass, KCallable, KFunction, KProperty
java.lang.Class 에 해당하는 KClass를 사용하면 클래스 안에 있는 모든 선언을 열거하고 각 선언에 접근하거나 클래스의 상위 클래스를 얻는 등의 작업이 가능하다.
interface KClass<T: Any> {
val simpleName: String?
val qualifiedName: String?
val members: Collection<KCallable<*>>
val constructors: Collection<KFunction<T>>
val nestedClasses: Collection<KClass<*>>
}
리플렉션 API 사용 예시 👇
class Person(val name: String, val age: Int)
// memberProperties 확장 함수 임포트
import kotlin.reflect.full.*
val person = Person("Alice", 29)
val kclass: KClass<Person> = person.javaClass.kotlin
kclass.simpleName
// 클래스 내부에 정의된 비확장 프로퍼티를 모두 가져온다
kclass.memberProperties.forEach {
val propertyName = it.name
val propertyValue = it.get(person)
}
위에서 사용한 memberProperties를 비롯해 KClass에 대해 사용할 수 있는 다양한 기능은 실제로는 kotlin-reflect 라이브러리를 통해 제공하는 확장 함수다.
implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.10"
아래 예제를 보면, ::foo 식의 값 타입이 리플렉션 API에 있는 KFunction 클래스의 인스턴스임을 알 수 있다.
fun foo(x: Int) = println(x)
val kFunction = ::foo
kFunction.call(42) // 리플렉션이 제공하는 call 을 사용해 호출할 수 있음
이번에는 내용이 굉장히 부실한데.. 이번 챕터는 거의 기록용 글이라고 보는 게 맞는 듯..
리플렉션과 친해지기 위해 직접 JSON 직렬화 라이브러리를 만들어보려고 한다. 만들고 후기를 올려보겠다 🚀🚀
'Kotlin' 카테고리의 다른 글
암스테르담에서 열린 KotlinConf 2023 참여 후기 (1) | 2023.04.24 |
---|---|
[Kotlin in Action] 9장. 제네릭스(Generics) - reified / variance / in / out (0) | 2022.01.30 |
[Kotlin in Action] 8장. 고차함수와 inline function (inline 함수의 장단점, 사용 이유 등) (0) | 2022.01.09 |
[Kotlin in Action] 4장. 클래스, 객체, 인터페이스 (0) | 2021.11.03 |
[Kotlin in Action] 3장. 함수 정의와 호출 (0) | 2021.10.09 |