티스토리 뷰

예전에 커스텀 어노테이션을 만드는 방법에 대해 글을 정리했었다.

https://pamyferret.tistory.com/47

 

custom annotation(커스텀 어노테이션) 만들기

개발을 하다보면 AOP 적용을 위해서 별도로 커스텀 어노테이션을 만들어야할 일이 생긴다. 물론 AOP 적용을 패턴을 통해 적용시킬 수 있지만 같은 공통 기능을 사용해야하는 메소드들에 공통점이

pamyferret.tistory.com

 

그 때 만들었던 커스텀 어노테이션은 단순히 속성 값을 하나만 받는 어노테이션이었다.

하지만 만일 하나의 속성에 대해 값을 여러 개 받아야하는 경우 어떻게 해야할까?

그럴 때는 해당 속성의 타입을 배열로 지정하면 간단하게 끝날 일이다. 참고로 List<T>와 같은 컬렉션은 어노테이션의 멤버로 설정이 불가하니 일반 배열을 사용해야한다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Animal {
    String[] value() default {};
}

 

// 어노테이션 멤버 이름이 value일 경우 value라는 것을 명시해주지 않아도 된다.
@Animal({"cat", "dog"})
public class Test {

}

 

보통 이렇게 배열로 어노테이션 멤버 타입을 정의해 속성 값을 여러 개 받곤 하는데 이 방법 말고 @Repeatable 어노테이션을 사용해서 받는 방법도 있어 정리해본다.

 

 

@Repeatable 사용 방법

우선 @Repeatable을 간단하게 설명해보면 동일한 어노테이션을 같은 곳에 중복해서 사용 가능하게 만들어주는 어노테이션으로 java 8부터 사용이 가능하다.

단, @Repeatable 어노테이션 사용만으로 끝나는게 아니라 몇 가지 처리가 필요하다.

@Repeatable 어노테이션의 안을 까보면 아래와 같이 value 값을 받도록 되어 있다. 여기서 value는 어노테이션 타입이어야 한다.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

 

value에 들어갈 어노테이션 타입은 중복해서 사용할 어노테이션을 묶을 일종의 컨테이너 어노테이션을 의미한다.

컨테이너 어노테이션은 하나로 묶고자 하는 어노테이션 배열을 멤버로 갖는다. 위의 Animal 어노테이션을 묶는 컨테이너 어노테이션을 만들면 아래와 같다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimalContainer {
    Animal[] value();
}

 

그리고 @Repeatable 어노테이션은 묶고자 하는 어노테이션 자체에 작성해주면 된다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(AnimalContainer.class)
public @interface Animal {
    String value() default "";
}

 

이렇게 @Repeatable을 정의하고나면 위에서 했던 것처럼 value에 여러 값을 넣고 싶을 때 배열이 아닌 어노테이션을 여러 번 쓰는 것으로 해결할 수 있다.

@Animal("cat")
@Animal("dog")
public class Test {
	...
}

 

 

@Repeatable 어노테이션을 사용하는 이유

기존에 배열 방식이 있는데 왜 @Repeatable 어노테이션을 사용할까? 별도로 컨테이너 어노테이션을 정의해야해서 복잡해보이긴 하지만 어노테이션에 여러 값을 받아야하는 속성에 따라 하나 하나 명시하는 것이 가독성면에서 @Repeatable 어노테이션을 이용하는 것이 더 좋을 수 있다.

 

또한 아래와 같이 어노테이션의 여러 멤버들의 조합으로 어노테이션이 다르게 동작해야한다면 배열로 멤버를 여러 개 받아서 조합을 하기보다는 @Repeatable을 통해 명시적으로 조합을 다르게 해 어노테이션을 중복 선언하게 하는 것이 좋다.

@Test(value1="aaa", value2=3, value4="test")
@Test(value1="bbb", value2=4, value4="blue")
...

 

 

@Repeatable 사용시 주의할 점

그렇다면 @Repeatable을 사용해 어노테이션을 중복 선언하게 했을 경우 주의해야할 점은 무엇일까?

우선 어노테이션과 컨테이너 어노테이션이 각기 다른 클래스라는 것에 주목해야한다. 이에 따라 어노테이션을 중복 선언한 것에 대해서는 컨테이너 어노테이션으로 값이 묶이지만 어노테이셔을 중복 선언하지 않을 것은 컨테이너 어노테이션으로 묶이지 않는다.

// 1. AnimalContainer로 묶인다.
@Animal("cat")
@Animal("dog")
@Component
public class Test1 {

}

// 2. Animal로 묶이지 않는다.
@Animal("cat")
@Component
public class Test2 {

}

 

위에 대해 아래와 같이 각각 Animal, AnimalContainer를 보유하고 있는지 확인해보면 결과가 다르다.

 @Test
    public void repeatableTest() {
        Test1 test1 = new Test1();
        Test2 test2 = new Test2();

        AnimalContainer animalContainer1 = test1.getClass().getAnnotation(AnimalContainer.class);
        Animal[] animals1 = test1.getClass().getAnnotationsByType(Animal.class);

        AnimalContainer animalContainer2 = test2.getClass().getAnnotation(AnimalContainer.class);
        Animal[] animals2 = test2.getClass().getAnnotationsByType(Animal.class);


        System.out.println(animalContainer1 == null); // false
        System.out.println(animals1.length); // 2

        System.out.println(animalContainer2 == null); // true
        System.out.println(animals2.length); // 1
    }

 

따라서 이 점을 인지하고 @Repeatable로 정의한 어노테이션을 사용하는 것이 좋다.

 

 

 

 

✋ @Repeatable

https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Repeatable.html

 

Repeatable (Java Platform SE 8 )

 

docs.oracle.com

 

 

 

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함