티스토리 뷰

# 이슈

annotation class CustomAnnotation

위와 같이 커스텀 애노테이션을 정의한다. 애노테이션은 다양하게 활용할 수 있지만 내 의도는 프로퍼티에 사용하려는 의도라고 가정한다.

class Person(
    name: String,
) {
    @CustomAnnotation
    val name: String = name
}

@Test                                                                           
fun findAnnotation() {                                                          
    val person = Person3("LichKing")                                            
    val properties = person::class.memberProperties                             
    for (property in properties) {                                              
        if (property.name == "name") {                                          
            assertThat(property.hasAnnotation<CustomAnnotation>()).isTrue()     
            return                                                              
        }                                                                       
    }                                                                           
                                                                                
    fail<Unit>("Must be not called")                                            
}

Person 클래스를 정의하고, 프로퍼티에 애노테이션을 붙였다. 그리고 kotlin reflection 을 통해 해당 애노테이션이 달렸는지 체크하는 테스트 코드를 작성했다. 테스트는 문제없이 통과한다.

하지만 현재 Person 과 같은 클래스를 정의할때 저런식으로 프로퍼티와 생성자를 분리해서 정의하는 것보다 코틀린은 좀 더 간결한 표현을 지원한다. 그리고 아마도 위와 같은 방식보단 아래에서 논의할 방식을 더 많이 쓰지 않을까싶다.

class Person(
    @CustomAnnotation
    val name: String,
)

문제는 이렇게 작성하면 위에서 통과한 테스트가 실패한다는 점이다.

 

# 해결

처음 이 문제를 맞닥뜨렸을때는 사실 잘 납득이 안돼서 꽤나 많은 삽질을 했었다. 찾아낸 해결법은 두 가지이다.

 

1. property:

class Person(
    @property:CustomAnnotation
    val name: String,
)

코틀린은 애노테이션을 사용할때 use-site targets 이라는 기능을 지원하는데 위 코드처럼 해당 애노테이션이 어떤 곳에서 사용되는지를 명시적으로 지정하는 것이다. 코틀린이 문법적으로 유연한 기능들을 많이 제공하기때문에 몇가지 모호한 상황들이 발생하는데, 그럴때 명시적으로 지정하는 기능이다. 저걸 붙여주면 해당 애노테이션이 property 에 사용된다는걸 인지하게된다.

 

2. @Target

1번 방식으로 해결된다는 점에서 문제의 원인이 뭔지 알 수 있게 되는데, @CustomAnnotation 이 내가 의도한 곳이 아니라 다른 곳에 적용되고 있다는 점이다. 어디에 적용되고 있는 것일까? 코틀린 annotation 문서를 보면 이런 내용이 있다.

If you don't specify a use-site target, the target is chosen according to the @Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:

param
property
field

코틀린 애노테이션은 애노테이션 정의를 좀 더 간결하게 하기위해 @Target 이 없으면 관례에 따라 동작하게 되는데, 그 관례에서 우선순위가 param 이 제일 높다. 그래서 생성자와 프로퍼티를 동시에 정의하는 형태의 클래스 문법에서는 생성자의 파라미터에 붙게 되는것이다.

그러다보니 리플렉션을 통해 프로퍼티를 탐색해봤자 애노테이션을 구할 수 없던 것이다.

 

참고로 제일 첫번째 클래스는 생성자와 프로퍼티가 분리되어있고, 프로퍼티에 애노테이션을 붙였기때문에 모호한 상황없이 프로퍼티로 동작할 수 있다.

 

그래서 @Target 을 명시적으로 지정해주면 문제를 해결할 수 있다.

@Target(AnnotationTarget.PROPERTY)
annotation class CustomAnnotation

class Person(
    @CustomAnnotation
    val name: String,
)

 

1번으로 해결할지, 2번으로 해결할지는 상황마다 다를것 같다. 타겟을 정적으로 명시할 수 있다면 2번을 해도 될것이고, 유연한 애노테이션으로 다양한 타겟을 지정하고싶을땐 1번으로 해결하면 될 것 같다. 개인적으로는 1번처럼 사용하는걸 별로 안좋아한다. ㅎㅎ

댓글
댓글쓰기 폼