자바 언어의 설계자들은 소스코드와 그에 대한 문서를 따로 관리하는 것보다 소스코드 자체에 문서 정보를 포함시키는 방식이 더욱 효율적이라고 판단하였습니다. 이에 따라 자바에서는 주석(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(); } |
@Native | static 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({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은 애너테이션이 어느 시점까지 유지되는지를 지정합니다. 기본값은 CLASS입니다.
| 값 | 설명 |
|---|---|
SOURCE | 소스 코드에만 존재, 컴파일 후 제거됨 |
CLASS | 클래스 파일(.class)에 존재하지만, 런타임엔 읽히지 않음 (기본값) |
RUNTIME | 클래스 파일에 존재하고, 런타임에도 리플렉션을 통해 참조 가능 |
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
...
}
프레임워크나 런타임 리플렉션 기반 처리 (예: Spring, Jackson 등)에 사용될 애너테이션은 반드시 RUNTIME으로 지정해야 합니다.
@Documented는 해당 애너테이션이 Javadoc API 문서에 포함되도록 지정합니다.
@Documented
public @interface PublicAPI {
...
}
기본적으로 애너테이션은 Javadoc에 표시되지 않습니다.
API 문서에 애너테이션 존재 여부를 명확히 하고 싶을 때 유용합니다.
@Inherited는 부모 클래스에 선언된 애너테이션이 자식 클래스에도 자동으로 적용되도록 지정합니다.
@Inherited
public @interface Role {
String value();
}
@Role("ADMIN")
class Parent {}
class Child extends Parent { }
// Child 클래스도 @Role("ADMIN")으로 인식됨
단, 클래스 간의 상속에만 적용되며, 인터페이스에는 적용되지 않습니다.
기본적으로 하나의 대상에는 동일한 애너테이션을 한 번만 사용할 수 있습니다.
그러나 @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인 경우에는 요소 이름 생략이 가능합니다.
@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()로 모든 애너테이션을 배열로 받아올 수 있습니다.