본문 바로가기
IT 공부/자바와 웹 애플리케이션

[자바] - 어노테이션 정보 읽는 방법

by exdus3156 2024. 1. 1.

1. 어노테이션이란

주렁주렁 메달려 있는 @어노테이션들...

어노테이션이란 간단히 말해, 소스 코드의 특정 부분에 대해 추가적인 설명을 해주는 메타 정보다.

주석과 비슷하다. 실제로 annotation이라는 영어 단어는 "주석"이라는 뜻을 가지고 있다. 어노테이션은 주석처럼 소스 코드의 로직에 전혀 전혀 영향을 미치지 못한다. 따라서 @어노테이션이 소스코드에 주렁주렁 메달려 있어도, 그냥 무시하고 소스코드에 작성된 진짜 로직을 읽으면 된다.

그러나 어노테이션이 일반 주석과 다른 점이 있다. 해당 소스코드(클래스)를 사용하는 다른 프로그램이 그 어노테이션 정보를 읽어들일 수 있다는 점이다. 읽어들인 정보를 바탕으로 원하는 로직을 수행할 수도 있는 것이다!

이것이 주석과 다른 점이다. 주석(//)은 컴파일 타임에 걸러져 완전히 정보가 삭제되지만, 어노테이션은 다른 프로그램을 위해서 실질적으로 넘겨주는 정보다.

따라서 어노테이션은 현재 소스코드의 로직에 전혀 영향을 미치지 못하며 그저 해당 부분을 설명해주는 추가 정보에 불과하지만, 다른 프로그램이 이 소스코드와 클래스를 읽어들일 때 어노테이션을 뽑아 사용할 수 있다는 점에 유의해야 한다.

JDK에 표준 어노테이션들이 정의되어 있다. 대부분 모르는 것이 있을 때 구글링을 해서 알아보는 정도로도 충분하다. 외우는 것보다 구글링을 해야 하는 이유는 현재 로직에는 전혀 영향을 미치지 못한다는 점, 그리고 다른 프로그램이 어떤 목적으로 사용하는지는 검색을 통해 알아도 충분하기 때문이다.

예를 들어, @Override 어노테이션은 컴파일러 프로그램에게, 이 메소드는 부모 클래스의 메소드를 상속한다고 알려준다. 따라서 부모클래스에서 이 메소드 선언을 발견하지 못하면 컴파일러는 에러를 내뱉는다. 컴파일러가 에러를 내뱉는 이유는 컴파일러 탓이다. 소스코드의 문제가 아니다. 이 말을 이해해야 한다.

 

 


2. 어노테이션 구현 코드

java.lang 패키지에 속하는 표준 어노테이션은 아무런 import 작업 없이 그대로 사용 가능하다. 그래서 간과하기 쉽지만 엄연히 어노테이션도 클래스나 인터페이스와 같은 코드로 구성되며 따라서 import 해야 한다.

즉, 어노테이션 또한 자바 코드로 작성되고, 컴파일되어 class 파일로 관리된다.

기본 구조는 아래와 같다.

public @interface MyAnnotation {
    // ...
}

interface 앞에 @이 붙은 형태다. 인터페이스와 다소 헷갈릴 수 있다. 하지만 이 키워드가 붙은 이유는 어노테이션을 사용하는 프로그램 측에서는 마치 인터페이스처럼 사용하기 때문이다.

public @interface MyAnnotation {
    int data();
    String name();
}

예를 들어, 위와 같은 어노테이션이 있을 때, 어노테이션 사용 프로그램 측에서는 '대략적으로'  아래와 같은 코드로 데이터를 가져올 수 있다.

public class Main {
    public static void main(String[] args) {
        // 1) 어노테이션이 사용된 클래스를 불러온다.
        Class<MyTest> cls = MyTest.class;
        
        // 2) 해당 클래스의 어노테이션 정보를 가져온다.
        MyAnnotation anno = cls.getAnnotation(MyAnnotation.class);
        
        // 3) 어노테이션 사용 시 전달된 데이터를 가져온다.
        int data = anno.data();
        String name = anno.name();
    }
}

어노테이션 또한 코드 상에서 위와 같이 사용될 수 있도록, 어노테이션은 궁극적으로는 java.lang.annotation.Annotation 인터페이스를 상속한 코드로서 동작한다. 그러나 명시적으로 상속을 붙이지 않아도 된다. @interface 라는 키워드에 암묵적으로 상속의 의미가 담기기 때문이다. 또한 Annotation 인터페이스는 다른 클래스가 상속하지 못하도록 제한되어 있다.

위 코드를 '대략적인' 코드라고 설명한 이유는, 어노테이션을 본인이 직접 커스터마이징해서 사용하지 않는 이상, 어노테이션은 사용 프로그램이 알아서 잘 사용할 것이라고 가정하고 나는 내 할 일에만 집중하는 것이 좋다고 생각하기 때문이다. 그냥 어노테이션 정보를 외부에서 사용할 수 있음을 충분히 보여주려는 의도였다.

따라서 수많은 어노테이션을 만나게 된다면, 이 어노테이션 정보로 외부 프로그램(자바 클래스)이 사용하겠구나... 하고 넘기면 된다. 

 

 


3. 작성된 어노테이션 코드 읽기

개인적으로 아직까진 어노테이션을 직접 구현해서 활용해보는 프로젝트를 만나보진 못했다. (내가 프레임워크를 만든다면 활용할 것 같기도..?)

그래서 어노테이션에 대해 아무리 공부해도 남는 기억이라곤 항상 "어노테이션 정보가 다른 프로그램에서 사용된다"는 인상 밖에 없었다. 적어도 지금까진 JDK 표준 어노테이션이나, 스프링처럼 어노테이션을 적극 활용하는 라이브러리, 롬복과 같이 소스코드 변경 어노테이션에서 어노테이션 코드를 읽는 것으로도 충분했다. 따라서 여기선 핵심 부분만 읽는 법에 대해서 정리해보겠다.

 

3-1. 어노테이션 요소 읽기

서블릿 컨테이너가 사용하는 어노테이션 중 @WebServlet 어노테이션 코드다. (※ 역코딩된 것)

인터페이스처럼 내부에 String name(); 같은 메소드들이 선언되어 있다. 이것이 어노테이션의 요소다. 어노테이션 사용 코드에서 봤듯이, 사용 프로그램 측에서는 메소드를 호출하는 식으로 어노테이션에 전달된 인자값을 읽는다.

default 는 만약 어노테이션의 인자로 아무것도 전달되지 않는 경우 사용할 디폴트 값이다. 이게 설정되어있지 않다면 반드시 인자로 값을 전달해야 한다.

타입이 무엇인지(어노테이션 타입조차 올 수 있을 정도로 유연하다), 배열인지 아닌지, 이름은 무엇인지, 디폴트값은 무엇인지 정도만 읽으면 충분하지 않을까 싶다.


3-2. 어노테이션 적용 대상 (@Target)

@Target은 어노테이션 타입에게만 올 수 있는 어노테이션이다. 이것은 어노테이션이 적용될 수 있는 대상을 가리킨다. 대상은 문자열이 아니라 java.lang.annotation.ElementType; 이라는 열거형 static 변수에 저장된 값을 사용한다. 요소가 ElementType[] 배열로 선언되었기 때문에 여러 개의 타입이 동시에 설정할 수 있다.

예를 들어, ElementType.TYPE클래스에 사용하는 어노테이션임을 말한다. ElementTYPE.METHOD메소드에 붙일 수 있는 어노테이션임을 말한다.

@WebServlet은 TYPE이므로 클래스나 인터페이스에 사용된다. 메소드나 변수에 사용해버리면 컴파일러가 오류를 일으킨다.


3-3. 유지 정책 (@Retention)

유지 정책이란, 어노테이션 정보를 언제까지 유지시킬 것인지, 언제까지 사용될 것인지 말해준다.

정책에는 총 세 가지가 있다. RetentionPolicy에 정의된 열거형 타입으로 정의되었다.

  • RetentionPolicy.SOURCE;
  • RetentionPolicy.RUNTIME;
  • RetentionPolicy.CLASS;

 

@Override

SOURCE는 소스 파일에만 존재할 뿐, 컴파일되고 난 class 파일에서는 삭제되는 정책이다. class 파일에서는 삭제되므로 런타임에 더 이상 어노테이션 정보를 찾을 수 없다. 따라서 소스코드 자체를 다루는 프로그램(주로 컴파일러)이 활용할만한 어노테이션이 온다. 예를 들어, @Override 같은 것들이다. 혹은 lombok도 있다. 

RUNTIME은 클래스 파일에 존재하면서도 런타임에 다른 프로그램이 실시간으로 이 정보를 불러와 동적으로 사용할 수 있는 정책이다. @WebServlet의 정책이 바로 RUNTIME이다. 당연하다. 프레임워크나 컨테이너 같은 프로그램은 실행하는 과정에서 클래스 파일을 불러와 메타데이터를 분석해야 하기 때문이다.

CLASS는 클래스 파일에는 존재하지만 런타임에 읽을 수 없는 정책이다. 읽을 수 없기 때문에 사실 그다지 사용되지 않는 정책 타입이다. 그럼에도 자바는 어노테이션 정의 시, @Retention을 설정하지 않으면 기본값으로 CLASS 정책을 부여한다.