애너테이션(Annotation)

ppp·2025년 7월 7일

Java 공부

목록 보기
10/13
post-thumbnail

개념과 등장 배경

자바 언어의 설계자들은 소스코드와 그에 대한 문서를 따로 관리하는 것보다 소스코드 자체에 문서 정보를 포함시키는 방식이 더욱 효율적이라고 판단하였습니다. 이에 따라 자바에서는 주석(comment)을 이용해 문서화할 수 있는 기능을 제공하며, 이를 위해 javadoc.exe라는 문서 생성 도구가 함께 제공됩니다.

주석 기반 문서화의 예시

다음은 JDK의 Annotation 인터페이스의 일부 소스코드입니다.

/**
 * The common interface extended by all annotation interfaces.  Note that an
 * interface that manually extends this one does <i>not</i> define
 * an annotation interface.  Also note that this interface does not itself
 * define an annotation interface.
 *
 * More information about annotation interfaces can be found in section
 * {@jls 9.6} of <cite>The Java Language Specification</cite>.
 *
 * The {@link java.lang.reflect.AnnotatedElement} interface discusses
 * compatibility concerns when evolving an annotation interface from being
 * non-repeatable to being repeatable.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {
	...

위 예제에서 볼 수 있듯이, @author, @since 등의 미리 정의된 태그(@ 태그)를 활용하여 소스코드에 대한 다양한 정보를 기술할 수 있습니다.

이러한 정보는 javadoc.exe 도구에 의해 HTML 문서로 변환되어 API 문서화에 활용됩니다.

애너테이션의 개념

이러한 문서화 주석 기능을 확장한 개념이 바로 애너테이션(annotation)입니다.
애너테이션은 다음과 같은 특징을 지니며, 자바 소스코드에 기계가 이해할 수 있는 정보를 부가적으로 포함시킬 수 있도록 해줍니다.

항목설명
정의주석과 유사하지만, 컴파일러나 다른 프로그램이 인식하여 처리할 수 있는 메타데이터
문법적 특징@ 기호로 시작
실행 영향일반적인 주석처럼 프로그램 실행에는 영향을 주지 않음
주요 활용 예시컴파일러 지시, 코드 자동 생성, 프레임워크 설정 등

애너테이션의 분류

자바에서 사용되는 애너테이션은 크게 다음과 같이 나눌 수 있습니다.

분류설명예시
표준 애너테이션JDK에서 기본 제공@Override, @Deprecated, @SuppressWarnings
메타 애너테이션애너테이션을 정의할 때 사용하는 애너테이션@Target, @Retention, @Inherited
사용자 정의 애너테이션개발자가 직접 정의프로젝트 또는 프레임워크에서 커스텀 용도로 사용
외부 라이브러리 애너테이션프레임워크나 도구에서 제공Lombok의 @Getter, JUnit의 @Test, Spring의 @Autowired

이 중 표준 애너테이션 및 메타 애너테이션은 java.lang.annotation 패키지에 포함되어 있으며, 자바 컴파일러나 런타임 환경에서 직접 인식하여 처리하는 용도로 사용됩니다.

표준 애너테이션 정리

자바에서는 애너테이션을 통해 코드에 다양한 의미와 제약 조건을 선언적으로 표현할 수 있습니다. 특히 JDK에서 기본적으로 제공하는 표준 애너테이션은 주로 컴파일러에게 정보를 전달하거나, 코드의 품질을 향상시키는 데 사용됩니다.

컴파일러용 애너테이션

애너테이션설명예시
@Override메서드가 상위 클래스나 인터페이스의 메서드를 오버라이딩하고 있음을 컴파일러에게 알림@Override public String toString() { ... }
@Deprecated더 이상 사용되지 않거나 사용이 권장되지 않는 API임을 명시@Deprecated public void oldMethod() { ... }
@SuppressWarnings컴파일 시 특정 경고 메시지를 억제@SuppressWarnings("unchecked")
@SafeVarargs제네릭 가변 인자(varargs) 사용 시 발생할 수 있는 타입 안정성 경고를 제거 (JDK 1.7+)@SafeVarargs public final <T> void print(T... args) { ... }
@FunctionalInterface해당 인터페이스가 함수형 인터페이스임을 명시 (JDK 1.8+)@FunctionalInterface interface MyFunc { void run(); }
@Nativestatic final 상수를 네이티브 코드에서 참조할 수 있도록 함 (JDK 1.8+)@Native public static final int ERROR_CODE = 42;
  • @Override는 반드시 붙이는 습관을 들이면 실수로 메서드 이름을 잘못 쓰는 오류를 줄일 수 있습니다.

  • @SuppressWarnings는 최소한의 범위에만 적용하고, 경고 메시지를 무시하기 전에 반드시 원인을 확인하는 습관이 필요합니다.

  • @FunctionalInterface는 컴파일러가 해당 인터페이스가 정확히 하나의 추상 메서드를 가지는지 확인해주므로, 람다식 사용 전 확인용으로 좋습니다.

  • @Retention(RetentionPolicy.RUNTIME)을 지정한 애너테이션은 리플렉션(reflection)을 통해 런타임에 정보를 읽어들일 수 있으므로 프레임워크에서 자주 활용됩니다.

메타 애너테이션

메타 애너테이션은 애너테이션 자체에 부가 정보를 제공할 때 사용하는 애너테이션입니다. 사용자 정의 애너테이션을 만들 때 자주 사용됩니다.

애너테이션설명예시
@Target애너테이션이 적용될 수 있는 대상을 지정 (예: 클래스, 메서드, 필드 등)@Target(ElementType.METHOD)
@Retention애너테이션의 유지 범위를 지정 (SOURCE, CLASS, RUNTIME)@Retention(RetentionPolicy.RUNTIME)
@Documented해당 애너테이션이 javadoc에 포함되도록 지정@Documented
@Inherited애너테이션이 자손 클래스에 자동으로 상속되도록 지정@Inherited
@Repeatable하나의 대상을 여러 애너테이션으로 반복 적용 가능 (JDK 1.8+)@Repeatable(Roles.class)

메타 애너테이션

@Target – 애너테이션 적용 대상 지정

@Target은 애너테이션을 적용할 수 있는 위치를 명시합니다.
여러 위치에 적용하려면 배열 형태로 중괄호 {}를 사용합니다.

@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
    ...
}

예를 들어, JDK의 @SuppressWarnings 애너테이션의 정의는 다음과 같습니다.

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

지정 가능한 위치 종류:

설명
ANNOTATION_TYPE애너테이션 타입
CONSTRUCTOR생성자
FIELD필드(멤버변수, enum 상수 포함)
LOCAL_VARIABLE지역 변수
METHOD메서드
PACKAGE패키지 선언
PARAMETER매개변수
TYPE클래스, 인터페이스, enum 등
TYPE_PARAMETER타입 매개변수 (JDK 1.8+)
TYPE_USE타입이 사용되는 모든 위치 (JDK 1.8+)

이 값들은 java.lang.annotation.ElementType 열거형에 정의되어 있습니다.

@Retention – 애너테이션 유지 범위 지정

@Retention은 애너테이션이 어느 시점까지 유지되는지를 지정합니다. 기본값은 CLASS입니다.

설명
SOURCE소스 코드에만 존재, 컴파일 후 제거됨
CLASS클래스 파일(.class)에 존재하지만, 런타임엔 읽히지 않음 (기본값)
RUNTIME클래스 파일에 존재하고, 런타임에도 리플렉션을 통해 참조 가능
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
    ...
}

프레임워크나 런타임 리플렉션 기반 처리 (예: Spring, Jackson 등)에 사용될 애너테이션은 반드시 RUNTIME으로 지정해야 합니다.

@Documented – Javadoc 포함 여부 지정

@Documented는 해당 애너테이션이 Javadoc API 문서에 포함되도록 지정합니다.

@Documented
public @interface PublicAPI {
    ...
}
  • 기본적으로 애너테이션은 Javadoc에 표시되지 않습니다.

  • API 문서에 애너테이션 존재 여부를 명확히 하고 싶을 때 유용합니다.

@Inherited – 자손 클래스에 상속 여부 지정

@Inherited는 부모 클래스에 선언된 애너테이션이 자식 클래스에도 자동으로 적용되도록 지정합니다.

@Inherited
public @interface Role {
    String value();
}

@Role("ADMIN")
class Parent {}

class Child extends Parent { }
// Child 클래스도 @Role("ADMIN")으로 인식됨

단, 클래스 간의 상속에만 적용되며, 인터페이스에는 적용되지 않습니다.

@Repeatable – 애너테이션 반복 사용 허용

기본적으로 하나의 대상에는 동일한 애너테이션을 한 번만 사용할 수 있습니다.
그러나 @Repeatable을 사용하면 같은 애너테이션을 여러 번 적용할 수 있습니다.

@Repeatable(ToDos.class)
public @interface ToDo {
    String value();
}

@ToDo("delete test codes")
@ToDo("override inherited methods")
class MyClass {
    ...
}

이때 컨테이너 애너테이션도 함께 정의해야 하며, value()라는 이름을 반드시 사용해야 합니다.

public @interface ToDos {
    ToDo[] value(); // 반드시 value 이름이어야 함
}

사용자 정의 애너테이션

자바에서는 @Override나 @Deprecated처럼 JDK에서 제공하는 표준 애너테이션 외에도, 개발자가 직접 애너테이션을 정의하여 사용할 수 있습니다.
이러한 사용자 정의 애너테이션은 다양한 상황에서 프레임워크 구성, 코드 메타데이터 관리, 커스텀 유효성 검증 등에 유용하게 활용됩니다.

애너테이션 정의 기본 문법

새로운 애너테이션은 @interface 키워드를 사용하여 정의합니다.

@interface 애너테이션이름 {
    타입 요소이름();
    ...
}
  • 애너테이션 내에 선언된 메서드를 애너테이션의 요소라고 합니다.

  • 애너테이션에도 인터페이스처럼 상수를 정의할 수 있지만, 디폴트 메서드는 정의할 수 없습니다.

복합 애너테이션 정의

@interface DateTime {
    String yymmdd();
    String hhmmss();
}

enum TestType { FIRST, FINAL }

@interface TestInfo {
    int count();
    String testedBy();
    String[] testTools();
    TestType testType();
    DateTime testDate(); // 다른 애너테이션 포함 가능
}

애너테이션 적용 예시

@TestInfo(
    count = 3,
    testedBy = "Kim",
    testTools = {"JUnit", "AutoTester"},
    testType = TestType.FIRST,
    testDate = @DateTime(yymmdd = "160101", hhmmss = "235959")
)
public class NewClass {
    ...
}
  • 요소 이름과 값을 모두 지정해야 하며, 값이 없는 경우 컴파일 오류가 발생합니다.

  • 이처럼 애너테이션은 다양한 타입의 요소를 포함할 수 있으며, 다른 애너테이션이나 enum 타입도 요소로 포함 가능합니다.

요소에 기본값 지정하기

요소에는 기본값을 지정할 수 있으며, 이를 통해 애너테이션 적용 시 값 생략이 가능합니다.

@interface TestInfo {
    int count() default 1;
}

@TestInfo // = @TestInfo(count = 1)
public class NewClass {
    ...
}

기본값으로는 null을 제외한 모든 리터럴(숫자, 문자, 문자열, boolean 등)이 허용됩니다.

value 요소 단축 표기

애너테이션의 요소가 단 하나이고 그 이름이 value인 경우에는 요소 이름 생략이 가능합니다.

@interface TestInfo {
    String value();
}

@TestInfo("passed") // = @TestInfo(value = "passed")
public class NewClass {
    ...
}

애너테이션 요소의 규칙 정리

규칙 항목설명
허용 타입기본형(primitive), String, enum, 애너테이션, Class만 허용
매개변수 선언불가능 (매개변수가 있는 메서드는 요소로 정의할 수 없음)
예외 선언불가능 (throws 사용 불가)
타입 매개변수불가능 (제네릭 타입 요소 선언 불가)

리플렉션을 활용한 접근

자바의 애너테이션은 단순히 선언하는 것뿐만 아니라, 프로그램 실행 중에 해당 정보를 읽어 동적으로 처리할 수 있다는 점에서 강력한 확장성을 제공합니다.
이를 위해 사용하는 기술이 바로 리플렉션(reflection)이며, 클래스에 선언된 애너테이션 정보를 런타임에 조회할 수 있습니다.

애너테이션 정보 읽기 – 예제 코드

import java.lang.annotation.*;

@Deprecated
@TestInfo(
    testedBy = "aaa",
    testDate = @DateTime(yymmdd = "160101", hhmmss = "235959")
)
class Ex12_8 {
    public static void main(String[] args) {
        Class<Ex12_8> cls = Ex12_8.class;

        // 단일 애너테이션 정보 가져오기
        TestInfo anno = cls.getAnnotation(TestInfo.class);
        System.out.println(anno.testedBy());
        System.out.println(anno.testDate().yymmdd());
        System.out.println(anno.testDate().hhmmss());

        // 모든 애너테이션 가져오기
        Annotation[] annoArr = cls.getAnnotations();
        for (Annotation a : annoArr) {
            System.out.println(a);
        }
    }
}

// 애너테이션 정의
@Retention(RetentionPolicy.RUNTIME)
@interface TestInfo {
    int count() default 1;
    String testedBy();
    String[] testTools() default "JUnit";
    TestType testType() default TestType.FINAL;
    DateTime testDate();
}

@Retention(RetentionPolicy.RUNTIME)
@interface DateTime {
    String yymmdd();
    String hhmmss();
}

enum TestType { FIRST, FINAL }
  • 모든 클래스 파일은 클래스 로더에 의해 메모리에 올라갈 때, 클래스에 대한 정보가 담긴 객체를 생성하는데 이 객체를 클래스 객체라고 합니다. 이 객체를 참조할 때는 클래스이름.class의 형식으로 사용합니다.

  • 클래스 객체에는 해당 클래스에 대한 모든 정보를 가지고 있는데, 애너테이션의 정보도 포함되어 있습니다.

  • getAnnotation()에 매개변수로 정보를 얻고자하는 애너테이션을 지정해주거나 getAnnotations()로 모든 애너테이션을 배열로 받아올 수 있습니다.

0개의 댓글