Kotlin Annotations #1 -Meta-Anotation

choi jeong heon
슬기로운 개발생활
8 min readNov 5, 2021

OverView

annotaiton 이란 사전적 의미로 주석 이라는 뜻을 가지고 있습니다.
kotlin docs 에서는 annotation을 코드에 붙이는 metadata 라고 설명합니다.
즉, 프로그램에게 추가적인 metadata를 제공하여 특별한 기능을 수행하도록 합니다.

kotlin에서 annotation을 선언하고 싶으면 class 앞에 annotation modifier를 붙여주면 됩니다.

//kotlin
annotation class MyAnnotation
//java
public @interface MyAnnotation

Annotation은 생성자를 가질 수 있고 파라미터를 받을 수 있습니다.

annotation class Special(val why: String)@Special("example") class Foo {}

가능한 파라미터는 다음과 같습니다.

  • Types that correspond to Java primitive types (Int, Long etc.)
  • Strings
  • Classes (Foo::class)
  • Enums
  • Other annotations
  • 위에 나열된 것들의 배열.

nullable한 파라미터는 가질 수 없습니다.

어노테이션의 인수로 클래스를 지정해야 하는 경우 Kotlin 클래스(KClass)를 사용합니다. Kotlin 컴파일러는 이를 자동으로 Java 클래스로 변환하여 Java 코드가 annotation 및 인수에 정상적으로 액세스할 수 있도록 합니다.

import kotlin.reflect.KClassannotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any>)@Ann(String::class, Int::class) class MyClass

Meta-Annotation

annotation classmeta-annotation 을 추가하여 추가 속성을 지정할 수 있습니다.

meta-annotation 은 4가지가 있습니다.

  • @Target
    annotation이 어디에 적용될건지 나타냅니다. (class, function, properties, expressions.
  • @Retention
    annotation이 컴파일된 클래스 파일에 저장되는지 여부와 런타임에 리플렉션을 통해 표시되는지 여부를 지정할 수 있습니다.(디폴트는 둘다 true)
  • @Repeatable
    단일 요소에서 동일한 주석을 여러번 사용할 수 있습니다.
  • @MustBeDocumented
    annotation이 public API 임을 나타내며, API 문서에 표시된 클래스 또는 메서드 signature가 포함되어야 함을 지정합니다.

@Target

annotation 이 어디에 적요될건지 나타냅니다. (class, function, properties, expressions…)
간단한 사용 예제를 먼저 살펴보겠습니다.

annotation 의 대상을 Function 으로 두었고 MyClass의 function에 적용해보았습니다.
for 문을 보시면, reflection 을 이용하여 어노테이션이 가진 값을 가져오고 있습니다.

meta-annotation 또한 annotation 이므로 어딘가에 정의가 되어 있습니다. 코드를 찾아보면,

@Target 은 meta-annotation 으로 자기자신을 가지고 있습니다.

그리고 varargAnnotationTarget 이라는 것을 받고 있는데 enum class 이며, annotation class가 어디에 적용될건지를 나타냅니다.

AnnotationTarget.ANNOTATION_CLASS을 사용하면 Annotation Class 에 사용할 수 있는 Annotation , 즉 meta-annotation 이 됩니다.

kotlin docs 예제도 살펴보겠습니다.

만약 생성자에서 annotation을 사용하고 싶다면 constructor 키워드 앞에 annotation을 추가하면 됩니다.

class Foo @Inject constructor(dependency: MyDependency) { ... }

프로퍼티 접근자에 사용하고 싶다면 ,

class Foo {
var x: MyDependency? = null
@Inject set
}

@Retention

annotation이 컴파일된 클래스 파일에 저장되는지 여부와 런타임에 리플렉션을 통해 표시되는지 여부를 지정할 수 있습니다.(디폴트는 둘다 true)

위의 @Target annotation 예시에서 리플렉션이 가능했던 이유는 @Retention 이 기본으로 @Retention(RetentionPolicy.RUNTIME) 으로 지정되기 때문입니다.
무슨말이냐면, @Target annotation 예시를 java 코드로 디컴파일 하면,

우리가 @Retention 을 추가하지 않았지만 @Retention(RetentionPolicy.RUNTIME) 으로 설정되어있습니다.

@Retention의 정의는 위와 같습니다. 또한 @Metadata 라는 어노테이션도 추가되어 있는데 이 annotation은 Kotlin 컴파일러에서 생성된 모든 클래스 파일에 있으며 컴파일러와 리플렉션에서 이 annotation을 읽습니다.

매개변수는 의도적으로 매우 짧은 JVM 이름을 갖습니다. 이러한 이름은 생성된 모든 클래스 파일에 나타나기 때문에 매우 짧은 값을 의도적으로 가지게 한다고 합니다.

AnnotationRetension 도 enum class 이며 아래와 같이 되어 있습니다.

Retention 은 annotation이 언제까지 유지되느냐를 나타내는데, 우리가 리플랙션에서 어노테이션 값을 가져올 수 있게 하려면 RUNTIME 으로 지정해야 합니다.

@Repeatable

단일 요소에 동일한 주석을 여러 번 사용할 수 있음을 나타냅니다. (JDK 1.8부터 추가)

불행히도 Kotlin은 java와 같은 @Repeatable을 아직 지원하지 않습니다.

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable

Kotlin의 @Repeatable 을 보면 그냥 선언만 되어 있습니다.

(kotlin 1.6 버전에 JvmRepeatable 이 포함될 예정인 것 같습니다. (jetbrain 개발자 피셜) 아래 사이트 참고)
https://youtrack.jetbrains.com/issue/KT-12794
https://github.com/Kotlin/KEEP/issues/257

java로 예시를 살펴보겠습니다.

annotation을 반복하기 때문에 @Repeatable 에 반복된 annotation 묶음을 관리하는 Container-Annotation 을 작성해야 합니다. (위에서는 Colors)

Repeatable의 코드를 찾아보면, RetentionPolicy 가 기본으로 RUNTIME으로 되어 있습니다.

Lambda

annotation은 람다에서도 사용할 수 있습니다. 람다의 본문이 생성되는 invoke() 메서드에 적용됩니다.

val f = @Myannotation { 
//...
}

참고 자료

--

--