@Retension의 RetentionPolicy를 3가지는 어떤 차이가 있고 언제 어떤걸 사용하는게 좋을지 고민을 하게 되었습니다.
그래서 실제 어노테이션 정책이 내부적으로 어떻게 작동하는지 테스트 코드를 작성하면서 확인해 봤습니다.
1.RetentionPolicy.SOURCE
//어노테이션 정의
@Retention(RetentionPolicy.SOURCE)
public @interface SourceAnnotation {
}
//어노테이션을 적용한 클래스
@SourceAnnotation
public class SourceObject {
}
//컴파일된 .class파일
public class SourceObject {
public SourceObject() {
}
}
컴파일된 .class파일에 @SourceAnnotation이 없어져 있는걸 알 수 있다.
즉, SOURCE정책을 적용한 어노테이션의 경우 컴파일 후 바이트코드로 변환되지 않는다.
2.RetentionPolicy.CLASS
//RetentionPolicy.CLASS정책 어노테이션
@Retention(RetentionPolicy.CLASS)
@interface ClassAnnotation {
String value();
}
//어노테이션을 적용한 클래스
@ClassAnnotation("Class")
public class ClassObject {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
//컴파일된 .class파일
@ClassAnnotation
public class ClassObject {
public ClassObject() {
}
}
//디컴파일 결과
Compiled from "ClassObject.java"
public class ClassObject {
public ClassObject();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
컴파일된 .class파일까진 살아 있지만, .class파일을 디컴파일 해보면 어노테이션이 사라져 있다.
그럼 왜 CLASS 정책을 쓸까? -> 라이브러리나 .jar파일엔 소스코드가 없고 .class파일만 있습니다.
즉, 리플랙션을 통해서 런타임에 동적으로 확인할 필요는 없지만 .class파일에는 남기고 싶은 경우 사용한다라고 보면 될 것 같습니다.
3.RetentionPolicy.RUNTIME
//어노테이션 정의
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
String RuntimeValue() default "";
}
//어노테이션을 적용한 .java파일
@RuntimeAnnotation
public class RuntimeObject {
}
//컴파일된 .class파일
@RuntimeAnnotation
public class RuntimeObject {
public RuntimeObject() {
}
}
런타임 시점에 자바의 리플렉션 기능을 사용해서 클래스의 메타데이터를 가져와 데이터에 접근 할 수 있게 된다.
위 .class파일을 디컴파일 해보자.
Classfile /path/to/TestClass.class
Last modified Mar 14, 2025; size 418 bytes
MD5 checksum f3g4h5i6...
public @RuntimeObject("runtimeValue") class TestClass
minor version: 0
major version: 61
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // TestClass
super_class: #3 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
Constant pool:
#1 = Methodref #3.#15 // java/lang/Object."<init>":()V
#2 = Class #16 // TestClass
#3 = Class #17 // java/lang/Object
#4 = Utf8 RuntimeObject
#5 = Utf8 runtimeValue
- @RuntimeObject가 디컴파일된 코드에 남아있다.
- Contant Pool에 어노테이션 이름 RuntimeObject와 어노테이션 속성값 runtimeValue가 기입되어 있다.
Reflection이 @RuntimeObject의 값 참조:
RuntimeObject runtimeObject = new RuntimeObject();
//인스턴스의 메타데이터 정보를 받아옴.
Class<? extends RuntimeObject> runtimeObjectClass = runtimeObject.getClass();
//@RuntimeAnnotation 메타데이터를 가져와 출력.
if (runtimeObjectClass.isAnnotationPresent(RuntimeAnnotation.class)) {
RuntimeAnnotation annotation = runtimeObjectClass.getAnnotation(RuntimeAnnotation.class);
System.out.println(annotation.RuntimeValue());
}
Reflection이 어떻게 Constant Pool에서 값을 가져오는 과정:
1. 클래스가 JVM에 로드되고 JVM이 Constant Pool을 사용해 어노테이션 정보를 찾는다.
2. Reflection API가 Contant Pool을 사용해 RuntimeObject와 runtimeValue 정보를 가져 옴.
Constant Pool
JVM이 실행 시 참조하는 메타데이터 저장소
정리:
1. SOURCE: 컴파일 후 .class에 입력되지 않아도 되는 어노테이션에 사용. 예:@Getter,@Setter.
2. CLASS: 바이트코드에는 입력되야 하지만 Reflection은 몰라도 되는 경우. 예:@NonNull
3. RUNTIME: 바이트코드에도 남아야하고 디컴파일 후 Contanst Pool에 들어가 Reflection이 알아야 되는 경우. @예:@Autowired.
'Language > JAVA' 카테고리의 다른 글
Comparable과 Comparator비교 (0) | 2025.03.12 |
---|