우테코 8기/Kotlin

Annotation과 Annotation Target

Reddish 2026. 6. 3. 16:38

Kotlin 에서 어노테이션의 정의는 아래와 같다.

코드에 있는 요소에 메타데이터를 부착하기 위해 사용하는 태그이다.

툴이나 프레임워크가 이 메타데이터를 확인하고 컴파일타임이나 런타임에 그 어노테이션에 따라 다른 행동을 하게 된다.

어렵다…………………영어도 어렵고 한글도 어렵고

annotate는 주석을 달다라는 뜻이다.

메타데이터는 단순히 정보를 붙이는데 끝나는 게 아니라, 그 정보를 바탕으로 컴파일러나 프레임워크 같은 도구들이 실제로 코드를 자동 생성 하거나, 동작을 제어하는 등의 작업을 처리한다.

코드 요소는 클래스, 함수, 프로퍼티 등에 해당한다.

따라서 정의를 조금 내 입맛대로 바꿔보자면 아래와 같다.

클래스, 함수, 프로퍼티 등의 코드 요소에 부가 정보를 붙여 툴이나 컴파일러, 프레임워크 등에게 특별한 동작을 수행하도록 하는 메타데이터

어노테이션을 사용하면 일반적으로 하는 일들을 자동화 해 단순화 할 수 있다. 보일러플레이트 코드 작성이나, 코딩 규칙을 강제하거나 문서 작성에 강점이 있다.

정의


클래스 선언을 할 때 annotation 키워드를 붙이면 어노테이션 선언이 가능하다.

annotation class MyAnnotation

적용

어노테이션은 인자를 넘겨줄 수 있다. 아래 @Deprecated의 코드는 그 예시를 보여준다

@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(
    val message: String,
    val replaceWith: ReplaceWith = ReplaceWith(""),
    val level: DeprecationLevel = DeprecationLevel.WARNING
)

// Deprecated 는 더 이상 사용되지 않음 이라는 뜻이다. 

// 기존 사용자에게 경고를 보내고 안전하게 새로운 기능으로
// 전환할 시간을 제공하기 위함이다.

우리는 타겟에 해당하는 코드에 @Deprecated코드를 붙이고 아래의 message, replaceWith, level 을 채워주면 해당 어노테이션을 쓸 수 있다.

@Deprecated("isDiscountable 을 대신 사용하세요")
fun applicableDiscount(
    items: List<CartItem>,
    shippingFee: Long,
    today: LocalDate = LocalDate.now(),
    now: LocalTime = LocalTime.now(),
): Long {
 // 로직
}

이런 식으로 Deprecated 어노테이션에 ()를 통해서 인자를 줄 수 있다.

인자를 지정 해 줄 땐 원시 타입의 값, 문자열, enum, 클래스 참조, 다른 어노테이션 클래스, 지금 말한 요소로 이뤄진 배열이 들어갈 수 있다.

  • 클래스를 어노테이션 인자로 지정할 땐 @MyAnnotation(MyClass::class) 처럼 ::class를 이름 뒤에 넣어줘야한다.
  • 다른 어노테이션을 인자로 지정할 때는 인자로 들어가는 어노테이션의 이름 앞에 @를 넣으면 안된다. 즉, annotation class로 선언하면 @ 를 사용하지 않는다.
  • 배열을 인자로 지정하려면 @MyAnnotation(path = arrayOf("/foo", "/bar")) 처럼 arrayOf 함수를 사용 해야한다.
    • 자바에서 선언한 어노테이션 클래스를 사용하면 value 파마리터가 필요에 따라 자동으로 가변 길이 인자로 변환된다. 그런 경우엔 @JavaAnnotation("abc", "foo", "bar") 처럼 arrayOf로 안써도 된다.

어노테이션으로 주어지는 인자는 컴파일 시점에 알 수 있어야 한다. 따라서 임의의 프로퍼티를 인자로 지정할 수 없다. 프로퍼티를 어노테이션 인자로 사용하려면 const 변경자를 붙여야한다. (컴파일러가 const가 붙은 프로퍼티는 상수로 취급하기 때문이다.)

컴파일 타임에 결정되지 않는 값은 어노테이션에 넘겨줄 수 없다.

요소의 범위


어노테이션은 코드 요소에 부착할 수 있다고 했는데, 이 요소는 어디 까지 일지, 그리고 어디엔 붙일 수 없을지 생각해봤다.

Kotlin의 아무 어노테이션이나 들어가서 (나는 Serializable에 들어갔다.) 어노테이션의 정의를 보면 뭔가 어디에 붙일지 정할 것 같은 어노테이션이 있다.

어노테이션이 타겟하는 값들을 지정할 수 있는데, 지금은 프로퍼티, 클래스, 타입에 붙일 수 있다고 나와있다.

실제로 해보면 아래와 같이 프로퍼티와 타입에도 부착이 가능한 것을 확인할 수 있었다.

함수에 작성하면 이런식으로 타겟을 설정할 수 없다고 나와있다. 처음엔 상위 함수만 실험해봤다가 내용에 “상위함수에서는 이 어노테이션을 붙일수 없다”고 뜨길래 아래 로컬 함수도 해봤는데 똑같은 결과가 나왔다.

(반대로 이를 통해 알 수 있는 건 로컬 함수에만 붙일수 있는 어노테이션도 있을 것 같기도 하고, 상위 함수에만 붙일 수 있는 어노테이션이 있을 것이라고도 알 수 있을 것 같다.)

 

@Target 에 존재하는AnnotationTarget에 들어가보자.
public final enum class AnnotationTarget private constructor() : kotlin.Enum<kotlin.annotation.AnnotationTarget> {
    CLASS,

    ANNOTATION_CLASS,

    TYPE_PARAMETER,

    PROPERTY,

    FIELD,

    LOCAL_VARIABLE,

    VALUE_PARAMETER,

    CONSTRUCTOR,

    FUNCTION,

    PROPERTY_GETTER,

    PROPERTY_SETTER,

    TYPE,

    EXPRESSION,

    FILE,

    @kotlin.SinceKotlin TYPEALIAS;
}

클래스, 어노테이션 클래스, 타입 파라미터, 프로퍼티, 필드, 지역 변수, 파라미터, 생성자, 함수, 프로퍼티의 게터와 세터, 타입, 표현식, 파일, 타입 별명(Alias)에다가 붙일 수 있다.

만약 @Target 어노테이션 없이 annotation class를 선언하고 타입에 해당 어노테이션을 붙이면 아래와 같이 된다.

data class TestAnnotation(
    val str: @MyAnnotation String = ""
)

annotation class MyAnnotation

  • 타입에는 사용할 수 없는 어노테이션이다
  • 클래스, 어노테이션 클래스, 프로퍼티, 필드, 지역 변수, 파라미터 값, 생성자, 함수, 게터 세터, 백킹 필드에서만 사용할 수 있다.

즉, 만약 @Target 어노테이션 없이 어노테이션 클래스를 생성한다면

클래스, 어노테이션 클래스, 프로퍼티, 필드, 지역 변수, 파라미터, 생성자, 함수, 게터, 세터, 백킹필드가 기본 Target으로 지정 되는 것 같다.

반대로 annotation class에 @Target만 지정하고 아무것도 안넣어준다면?

@MyAnnotation
data class TestAnnotation(
    val str: String = ""
)

@Target
annotation class MyAnnotation

tagets에 아무것도 없다. 기본적인 타입들을 제공하지 않고, 지정을 해줘야 하는 것 같다.

Target 객체를 살펴보면 아래와 같다.

@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)

사용 지점 대상(use-site target)

Kotlin In Action 에서는 @Rule 어노테이션을 예시로 들고 있다.

코틀린에서 프로퍼티는 기본적으로 자바 필드와 게터 메서드와 대응된다. (즉, val 로 선언하면 값과 getter가 같이 선언된다.) 만약 변경 가능하다면 세터와 세터 파라미터가 추가될 것이다. (var의 경우 setter와 fun setter에 파라미터로 전달될 인자를 말한다.) 주생성자에서 프로퍼티를 선언하면 위와 같은 것들과 자바 생성자 파라미터와 대응될 것이다.

따라서 어노테이션을 붙일 때 이런 요소들 중에서 어떤 요소에 어노테이션을 붙일지 표시해야 할 필요가 있고, 이를 가능하게 해주는게 사용 지점 대상(use-site target) 이다. 이것을 선언해 어노테이션을 붙일 요소를 정할 수 있다.

@get:Rule

위와 같은 방식으로 하면, 프로퍼티의 getter에 해당 어노테이션을 적용할 수 있다.

class HasTempFolder {
	@get:Rule // 프로퍼티가 아니라 게터에 어노테이션이 붙게 된다.
	val folder = TemporaryFolder()
}

자바에 선언된 어노테이션을 사용해 프로퍼티에 어노테이션을 붙이면 기본적으로 필드에 어노테이션이 붙는다. 코틀린으로 어노테이션을 선언하면 프로퍼티에 직접 사용할 수 있는 어노테이션을 만들 수 있다.

사용 지점 대상을 지원하는 목록

  • property: 프로퍼티 전체로, 자바에서 선언된 어노테이션에선 이 사용 지점 대상을 사용할 수 없다.
  • field: 프로퍼티에 의해 생성되는 뒷받침하는 필드
  • get: 프로퍼티 게터
  • set: 프로퍼티 세터
  • receiver: 확장 함수나 프로퍼티의 수신 객체 파라미터
  • param: 생성자 파라미터
  • setparam: 세터 파라미터
  • delegate: 위임 프로퍼티의 위임 인스턴스를 담아둔 필드
  • file: 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스

자바와 달리 코틀린에선 어노테이션 인자로 클래스나 함수 선언이나 타입 외에 임의의 식을 허용한다.

val strings = products as List<String> // products는 다른 곳에서 이미 선언한 리스트

해당 코드를 붙여넣으면 as List<String> 에 Unchecked cast of 'List<Product>' to 'List<String>'. 와 같은 경고 문구가 뜬다. 안전하지 못한 타입캐스팅 경고를 의미한다.

@Suppress("UNCHECKED_CAST")
val strings = products as List<String>

로 사용하면 경고 문구를 무시할 수 있다.

즉, as List<String> 과 같은 식에서도 어노테이션을 적용 가능하다는 의미이다.