어노테이션 반복정의를 위한 @Repeatable 작성법과 주의점

|

개발시 어노테이션을 많이 이용하는 편인데 종종 하나의 클래스, 또는 메소드에 여러 속성을 정의하고 싶을때가 있습니다.
JDK ~1.7 에서는 아래와 같은 방식으로 정의가 가능했습니다.

// case 1
@GreenColor
@BlueColor
@RedColor
public class RGBColor { ... }

// case 2
@Color(colors={"green", "blue", "red"}
public class RGBColor { ... }

JDK 1.8부터는 같은 어노테이션을 중복정의 가능한 @Repeatable 어노테이션을 제공합니다.

@Repeatable Tutorial

JavaDoc에 정의된 정식 설명은 아래와 같습니다.

The annotation type java.lang.annotation.Repeatable is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. The value of @Repeatable indicates the containing annotation type for the repeatable annotation type.

@Repeatable 어노테이션을 이용하면 다음과 같이 정의가 가능해집니다.

@Color("green")
@Color("blue")
@Color("red")
public class RGBColor { ... }

어노테이션을 정의하는 방법은 실 사용할 어노테이션과 그 어노테이션 묶음 값을 관리하는 컨테이너 어노테이션을 작성해야 합니다.

@Repeatable(value = Colors.class)
public @interface Color {}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Colors {
    Color[] value();  
}

위와 같이 정의시 아래와 같은 방법으로 해당 클래스에 정의된 어노테이션을 뽑아올 수 있습니다.

Colors colors = this.getClass().getAnnotation(Colors.class);

주의점

위와 같이 설정된 상태에서 아래와 같이 클래스를 작성하게 될 경우 이슈가 있습니다.

public class AnnotationTest {
    @Repeatable(value = Colors.class)
    public @interface Color {
        String value();
    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Colors {
        Color[] value();
    }

    @Color("green")
    @Color("blue")
    @Color("red")
    public class RGBColor {}

    @Color("green")
    public class GreenColor {}

    @Test
    public void repeatableAnnotationTest() {
        RGBColor rgbColor = new RGBColor();
        GreenColor greenColor = new GreenColor();

        Colors rgbColors = rgbColor.getClass().getAnnotation(Colors.class);
        Color[] rgbColorArray = rgbColor.getClass().getAnnotationsByType(Color.class);

        Colors greenColors = greenColor.getClass().getAnnotation(Colors.class);
        Color[] greenColorArray = greenColor.getClass().getAnnotationsByType(Color.class);

        System.out.println("rgbColors : " + rgbColors);
        System.out.println("rgbColorArray : " + rgbColorArray);
        System.out.println("rgbColorArray.length : " + (rgbColorArray != null ? rgbColorArray.length : 0));
        System.out.println("greenColors : " + greenColors);
        System.out.println("greenColorArray : " + greenColorArray);
        System.out.println("greenColorArray.length : " + (greenColorArray != null ? greenColorArray.length : 0));
    }
}

테스트 코드를 돌려보면 결과는 아래와 같이 나옵니다.

rgbColors : @com.tmoncorp.module.sduf.test.common.AnnotationTest$Colors(value=[@com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=green), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=blue), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=red)])
rgbColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@1996cd68
rgbColorArray.length : 3
greenColors : null
greenColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@3339ad8e
greenColorArray.length : 0

GreenColor의 클래스는 @Color어노테이션이 1개만 정의되어 있어 @Colors로 묶이지 않고 getAnnotation로 가져올 경우 null을 반환하게 됩니다.
그리고 @Color어노테이션이 1개만 정의되어 있을 경우 @Colors로 묶이지 않아 Retention Policy 정책이 생성되지 않으므로 getAnnotationsByType 메소드 호출시 반환값이 0개가 됩니다.

위와 같은 현상을 해결하기 위해서는 하위 어노테이션 정의시 @Retention을 정의해주고 getAnnotationsByType메소드를 이용하여 값을 찾으면 정확한 반환값을 찾을 수 있습니다.

    @Repeatable(value = Colors.class)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Color {
        String value();
    }

실행하면 아래와 같이 값이 존재하는것을 확인할 수 있습니다.

rgbColors : @com.tmoncorp.module.sduf.test.common.AnnotationTest$Colors(value=[@com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=green), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=blue), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=red)])
rgbColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@19e1023e
rgbColorArray.length : 3
greenColors : null
greenColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@7cef4e59
greenColorArray.length : 1

참고

Oracle JDK Doc - Annotation Type Repeatable
Java 8 Repeating Annotations Tutorial using @Repeatable with examples

Comments