namkyujin.com

2017-03-14
#java

자바의 어노테이션

JDK1.5 부터 제공된 기능인 어노테이션은 @(;AT) 으로 시작하는 주석의 한 형태를 말한다. @Override, @SuppressWarnings("") 과 같은 어노테이션에 익숙할 것이다.

어노테이션은 메타데이터 한 형태로, 프로그램에 대한 정보를 제공하지만, 그 프로그램의 일부는 아니다. 어노테이션을 설정한 코드에 직접적인 영향을 미치지는 않는다.

Oracle의 자바 튜토리얼 문서에서 어노테이션을 위와 같이 정의하며 용도를 설명한다.

  • 컴파일러를 위한 정보를 제공하기 위해.
  • 컴파일 시점에 어떤 코드나 XML 파일 등을 생성하기 위해.
  • 런타임 시점에 추가적인 처리를 하기 위해.

어노테이션의 형태

1@Override
2public String toString() {
3	// ...
4}

@(;AT) 기호는 컴파일러에 기호 다음에 오는 것이 어노테이션임을 알린다. 위 예제에서 어노테이션명은 Override 이다.

미리 정의 된 어노테이션들

자바에서 바로 사용할 수 있도록 미리 정의 되어 있는 어노테이션들은 다음과 같다.

  • @Deprecated
  • @Override
  • @SuperWarnings
  • @SafeVarargs
  • @FunctionalInterface

이 어노테이션들에 대한 더 자세한 정보는 자바 튜토리얼 문서에서 확인할 수 있다.

어노테이션 직접(Custom) 정의

어노테이션은 필요에 따라 직접 정의할 수도 있다. 자바 튜토리얼에서 제공한 예시는 클래스에 대한 주석을 어노테이션으로 대체하는 것이다.

1// Author: iamkyu
2// Date: 2017/03/11
3// Current revisision: 2
4// Last modifed: 2017/03/12
5class MyClass {
6	// ...
7}

이 주석들을 어노테이션으로 전환하려면 하나의 특별한 인터페이스를 선언해야 한다.

1@interface ClassPreamble {
2    String author();
3    String date();
4    int currentRevision() default 1;
5    String lastmodified() default "N/A";
6}

이처럼 어노테이션을 직접 정의할 때 해당 어노테이션의 적용 가능 대상, 정보 유지 시간 등도 임의로 설정할 수 있다. 이를 위해 @Target, @Retentiion, @Documented, @Inherited 의 어노테이션을 사용하고 이것들을 메타(Meta) 어노테이션이라고 한다.

인터페이스를 선언한 후, @ClassPreamble 이라는 이름의 어노테이션을 사용할 수 있다.

1@ClassPreamble (
2   author = "iamkyu",
3   date = "2017/03/11",
4   currentRevision = 2,
5   lastModified = "2017/03/12",
6)
7class MyClass {
8    // ...
9}

어노테이션과 리플렉션

사실 내가 궁금했던 것은 어노테이션은 어떻게 작동하는 것인가에 대해서였다. 글의 첫 부분에서 인용한 어노테이션의 정의를 다시 보자.

어노테이션은 메타데이터 한 형태로, 프로그램에 대한 정보를 제공하지만, 그 프로그램의 일부는 아니다. 어노테이션을 설정한 코드에 직접적인 영향을 미치지는 않는다.

어노테이션 자체가 코드에 직접적인 영향을 주지는 않는다. 그렇다면 어딘가에서 어노테이션을 인식하고 그 인식에 따라 어떤 처리 하는 코드가 또 있다는 것인데 ‘어디서 어떻게’ 하는 것인지 궁금했다.

java.lang.annotation.Annotation 인터페이스

먼저, 모든 어노테이션은 java.lang.annotation.Annotation 인터페이스를 상속한다.

Annotation 상속구조

ClassPreamble 은 이 글의 어노테이션 직접 정의 부분에서 선언했던 커스텀 어노테이션인데, 직접 상속을 구현하지 않았는데 Annotation 인터페이스를 상속하고 있다. @interface 라는 메타 어노테이션을 통해 처리되는 듯하다.

스프링프레임워크의 커스텀 어노테이션

순수 자바 코드에서 어노테이션을 찾아서 메타데이터를 읽어 들이는 부분을 찾기가 힘들었다. 그래서 스프링 프레임워크 상에서 커스텀 어노테이션을 처리하는 방식을 추적해보기로 했다. 스프링 프레임워크에서는 @Controller, @Service 등 다양한 커스텀 어노테이션이 기본적으로 내장(Built-In) 되어 있다.

스프링프레임워크를 부트스트랩(Bootstrap) 하는 과정 중, 컨텍스트 설정을 읽어오는 부분이 있다. 이 코드를 따라 들어가다 보면 MetaAnnotationUtils 라는 클래스가 등장하는데 문서에 따르면 어노테이션을 찾거나 얻어올 뿐만 아니라 추가적인 기능도 지원한다고 한다.

이 유틸리티 클래스 중 findAnnotationDescriptor 메서드의 일부를 보면 아래와 같다.

1for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
2	// ...
3}

clazz 변수는 Class 타입이다. 이를 통해 추측할 수 있는 것은 자바의 리플렉션 기술을 통해 클래스에 선언 된 어노테이션을 읽어들일 수 있다는 것이다.

예시

 1@Target(ElementType.TYPE)
 2@Retention(RetentionPolicy.RUNTIME)
 3public @interface ClassPreamble {
 4    String author();
 5    String date();
 6    int currentRevision() default 1;
 7    String lastModified() default "N/A";
 8    String lastModifiedBy() default "N/A";
 9    String[] reviewers();
10}
11
12////////////////////////////////
13
14@ClassPreamble(
15        author = "Tester",
16        date = "2017/03/11",
17        reviewers = {"John", "Allen"}
18)
19public class CustomAnnotation {
20	// ...
21}

위의 코드와 같이 커스텀 어노테이션을 정의하고 해당 어노테이션을 사용한 클래스가 있다고 했을 때, 다음과 같은 방법으로 어노테이션의 메타데이터를 읽어올 수 있다.

 1@Test
 2public void getMetadataTest() {
 3    //given
 4    Class clazz = new CustomAnnotation().getClass();
 5    Annotation annotation = clazz.getAnnotation(ClassPreamble.class);
 6
 7    //when
 8    String result = annotation.toString();
 9    // @lang.annotation.ClassPreamble(currentRevision=1, lastModified=N/A, lastModifiedBy=N/A, author=Tester, date=2017/03/11, reviewers=[John, Allen])
10
11    //then
12    assertTrue(result.contains("currentRevision=1"));
13    assertTrue(result.contains("lastModified=N/A"));
14    assertTrue(result.contains("lastModifiedBy=N/A"));
15    assertTrue(result.contains("author=Tester"));
16    assertTrue(result.contains("date=2017/03/11"));
17    assertTrue(result.contains("reviewers=[John, Allen]"));
18}

참고