본문 바로가기
프로그래밍/kotlin

[kotlin] 코틀린 차곡차곡 - 11. Annotation ( 어노테이션 )

by 사바라다 2021. 8. 28.

안녕하세요. 오늘은 코틀린에서 Annotation을 사용하는 방법에 대해서 알아보도록 하겠습니다.

어노테이션( Annotation )

어노테이션은 자바 또는 코틀린에서 사용됩니다. 어노테이션은 메타데이터 ( 부가기능 )을 코드에 비침투적으로 추가할 수 있는 수단으로 kotlin docs에서는 정의하고 있습니다. 어노테이션은 멤버 변수, 함수, 클래스 등 다양한 곳에 위치 시킬 수 있으며 또한 다양한 기능을 가진 다양한 어노테이션이 있습니다. 그리고 우리는 기 정의된 어노테이션 뿐만 아니라 스스로 정의한 커스텀 어노테이션을 만들어 사용할 수도 있습니다.

기본적인 선언 및 사용방법

코틀린에서 어노테이션은 아래와 같이 선언하면 사용할 수 있습니다. 아래 코드는 Karol이라는 어노테이션을 제작한 것이며 @Karol으로 사용할 수 있습니다.

annotation class Karol

선언한 어노테이션은 아래 코드와 같이 다양한 곳에 붙여서 사용할 수 있습니다. 물론 현재 코드는 아무런 정의가 되어있지 않기 때문에 @Karol이라는 이름을 달아준다. 여기에 기능을 추가하기 위해서는 AOP를 이용할 수도 있으며 reflection을 이용하여 Annotation을 체크 후 기능을 만들어 줄 수 도 있습니다.

@Karol
class Sabarada(
    @Karol val first: Int
) {
    @Karol
    fun baz(@Karol second: Int): Int {
        return first + second
    }
}

어노테이션의 추가 속성

어노테이션에 추가로 속성을 달아줄 수 있습니다. 추가 속성은 어노테이션 위에 또 다른 어노테이션을 달아주는 것입니다. 코틀린에서 사용할 때 거의 필수적으로 사용하는 추가 속성의 대표적인 예는 아래와 같습니다.

  • @Target : 어노테이션을 달 수 있는 구성 요소 선정
    • 타겟에 선언되지 않고 해당 타켓에 사용하면 정상적으로 동작하지 않을 수 있습니다.
    • AnnotationTarget enum 클래스 활용
  • @Retention : 어노테이션이 남아있는 단계를 선정합니다.
    • 소스(SOURCE), 컴파일 타임(BINARY), 런타임(RUNIME) 중 선택 가능
    • 런타임으로 선언하면 런타임중에 어노테이션 정보가 남아있습니다. 그렇지 않으면 어노테이션 정보는 지워집니다.
    • AnnotationRetention enum 클래스 활용

이런 추가 속성 어노테이션은 아래처럼 사용할 수 있습니다.

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Karol

사용하는 곳

코틀린에서 어노에테이션을 사용할 수 있는 타입은 AnnotationTarget enum 클래스에 정의되어 있다고 말씀드렸습니다. 설정 가능한 곳은 아래와 같습니다.

  • 클래스
  • 어노테이션 클래스
  • 프로퍼티 ( 멤버변수 )
  • 필드 ( 프로퍼티의 백킹 필드 포함 )
  • 지역변수
  • 값 파라미터
  • 생성자
  • 함수
  • 게터 (Getter)
  • 세터 (Setter)
  • 표현식 (Expression)

커스텀 어노테이션

그렇다면 실제로 커스텀 어노테이션을 만들어 보도록 하겠습니다. 만들어서 최종 완성하려고 하는 어노테이션은 아래와 같습니다. 추가적으로 설명드리자면 아래의 어노테이션 예제는 클래스 레벨 제약조건(Constraint)를 걸기 위해서 작성한 어노테이션입니다.

@Target(
    AnnotationTarget.ANNOTATION_CLASS,
    AnnotationTarget.CLASS
)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = [KarolValidator::class])
annotation class ValidKarol(
    val message: String = "Your custom message",
    val groups: Array<KClass<*>> = [],
    val payload: Array<KClass<out Payload>> = []
)

Java의 어노테이션과 크게 달라지지 않았다라고 봐도 사실 문제가 없어 보입니다. 위에서 알아본 것 이외에 있는 부분은 추가 속성 어노테이션인 @Constraint와 내부 변수들 정도 인것 같습니다. 여기서 어노테이션의 내부 변수 선언 에 대해서 한번 짚고 넘어가도록 하겠습니다.

annotation의 primary constructor 사용

기존의 자바에서의 어노테이션과 다르게 코틀린의 어노테이션의 내부 변수 선언은 primary constructor를 이용합니다. 기존에 자바에서는 일반 메서드를 선언하듯 String message () 이런식으로 했었지요. 그리고 여기에 선언할 수 있는 변수의 타입은 아래와 같습니다.

  • Java 기준 primitive type
    • Int, Long, Double, Char 등등
  • String
  • Enum
  • KClass 타입
  • 위의 언급한 선언 가능한 타입의 Array

기본값(default) 값 선언 방법

또 다른 부분은 default 값 선언 부분입니다. 자바에서 기본값 선언은 default 키워드를 이용했습니다. 예를 들어 message 라는 변수에 기본값을 주는 방법은 String message () default "Your custom message" 정도였습니다. 하지만 코틀린은 일반 변수에 기본값 선언하듯 val message: String = "Your custom message" 이렇게 선언하면 되도록 바뀌었습니다.

Class Type 선언

클래스 타입을 사용하는 방법이 조금 바뀌었습니다. 자바에서는 Class로 클래스 타입을 선언하였고 이용하였습니다. 예를 들면 Class<?>[] groups () default {} 이런느낌이지요. 하지만 코틀린으로 넘어오면서 Class는 코틀린 클래스인 KClass와 자바 클래스인 Class를 모두 이용할 수 있게 되었습니다. 여기서 어노테이션에서는 자바 클래스인 Class는 사용하지 못하는 것으로 확인하였습니다. 코틀린에서는 코틀린 클래스인 KClass를 val groups: Array<KClass<*>> = [] 와 같이 선언해주시면 됩니다.

자바라면 ?

글로만 들으면 자바와의 차이가 잘 안 와 닿을 수 있을것이라 생각했습니다. 그래서 자바 코드를 가져와 보았습니다. 아래는 위의 코틀린 예제와 동일한 자바코드입니다. 둘을 비교해보면 잘 이해가 되실것 같습니다.

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = KarolValidator.class)
@interface ValidKarol {
    String message () default "Your custom message";
    Class<?>[] groups () default {};
    Class<? extends Payload>[] payload () default {};
}

마무리

오늘은 이렇게 코틀린에서 Annotation을 사용하는 방법에 대해서 알아보는 시간을 가져보았습니다.

다음 코틀린 시간에는 reflection에 대해서 알아보도록 하겠습니다.

감사합니다.

참조

kotlinlang_annotations

baeldung_kotlin_annotations

댓글