본문 바로가기
Language/JAVA

@Retension (SOURCE,CLASS,RUNTIME)

by KeepCoding 2025. 3. 14.

@RetensionRetentionPolicy를 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