아무 관심 없던 @Retention 어노테이션 정리(RetentionPolicy SOURCE vs CLASS vs RUNTIME)
@Retention
annotation
관심 갖게 된 이유
자바에서 지향하는 방법은 아니지만 필요에 의해서 커스텀 애노테이션(Annotation)을 만들어야 할 때가 있습니다.
보통 예제 샘플 코드를 보면 메타 애노테이션으로 항상 붙어있고 RetentionPolicy=RUNTIME으로 되어습니다.
그렇기 때문에 그럴 때나 보게되는 애노테이션이라 @Retention
은 무시하고 @Target
정도만 확인하고 써왔습니다.
명확히는 @Retention
이라는 애노테이션이 있는지도 모르고 있었습니다.
그러던 중 회사 코드에서 JPA 엔티티(Entity) 클래스의 필드에 롬복(lombok)의 @NonNull
이 쓰인 것을 확인하고 이게 동작하는지가 궁금해지면서 찾아보게 되었습니다.
설명을 읽어보면 @Retention
애노테이션은 애노테이션의 라이프 사이클 즉, 애노테이션이 언제까지 살아 남아 있을지를 정하는 것입니다.
SOURCE vs CLASS vs RUNTIME
@Retention
어노테이션의 속성으로 RetentionPolicy
라는 것이 있습니다.
여기에 올 수 있는 값은 source, class, runtime 이렇게 3가지가 있습니다. (공부하고나니 이름이 굉장히 직관적입니다.)
- RetentionPolicy.SOURCE : 소스 코드(.java)까지 남아있는다.
- RetentionPolicy.CLASS : 클래스 파일(.class)까지 남아있는다.(=바이트 코드)
- RetentionPolicy.RUNTIME : 런타임까지 남아있는다.(=사실상 안 사라진다.)
이게 SOURCE와 RUNTIME은 대~충 이해가 가는데 CLASS는 뭔가 싶었습니다만, 아래 예시와 함께 이해해보면 될 것 같습니다.
RetentionPolicy.SOURCE
롬복(lombok)의 @Getter
, @Setter
같은 것을 살펴보면 RetentionPolicy.SOURCE
로 되어 있는 것을 확인할 수 있습니다.
SOURCE 정책이기 때문에 .java 소스 파일까지는 애노테이션이 남아있고 컴파일되어 클래스파일이 되면 사라질 것으로 추정됩니다.
정확히 디컴파일까지 해보진 않았지만 아마도 @Getter
라는 애노테이션은 찾아볼 수 없을 것입니다.
단, 주의해야할 것은 롬복(lombok) 라이브러리 특성상 코드 생성을 도와주는 것이라서 @Getter
가 컴파일될 때 사라지는 대신, 실제 getter 코드가 바이트코드로 생성될 것입니다.
따라서 디컴파일했을 때는 @Getter
애노테이션은 사라졌지만 소스코드에 작성하지 않았던 실제 getter 메소드가 생기는 것을 확인할 수 있을 것입니다.
RetentionPolicy.CLASS
롬복(lombok)의 @NonNull
을 살펴보면 RetentionPolicy.CLASS
로 되어 있는 것을 확인할 수 있습니다.
CLASS 정책이기 때문에 .class 파일까지는 애노테이션이 살아있고 런타임에서 클래스로더가 해당 클래스를 읽어오면 사라질 것으로 추정됩니다.
추정해보기 전에 우선은 @NonNull
에 대한 설명을 읽어보겠습니다.
"파라미터에 @NonNull을 쓰면, 해당 파라미터를 사용한 메소드 시작 부분 또는 생성자의 바디(body) 부분에 null 체크 로직을 삽입해주고, 필드에 @NonNull을 쓰면, 해당 필드에 값을 할당하는 어떤 생성된 메소드 또한 null 체크 로직을 삽입해준다."
이것도 코드 생성이라는 관점에서 @Getter
와 마찬가지로 RetentionPolicy.SOURCE로 해도 될 것 같은데 왜 CLASS를 택했는지는 정확히 잘 모르겠습니다. -> 댓글에 친절하게 설명해주신 글이 있습니다. 참고하세요!
틀릴 확률이 높은 추측이긴 한데 리플렉션이나 역직렬화(Deserialize)와 관련이 있지 않을까 했습니다. (리플렉션은 런타임 기술인 것 같은데... 아무튼...)
컴파일 단계에서 생성자나 setter같이 필드에 할당하는 모든 메소드에 롬복이 열심히 null 체크 로직을 넣어놨겠습니다만,
우회적으로 필드를 찾아서 값을 할당하는 리플렉션(?)같은 기술 또는 자바 객체로 Deserialize시킬 때 에도 null 체크 로직을 넣어주려고 바이트코드까지 남기지 않았을까? 개인적인 추측을 해봤습니다. (지나가시는 고수분은 왜 그런지 댓글로 알려주시면 감사하겠습니다...)
RetentionPolicy.RUNTIME
굳이 예시로 들 것도 없는게 대부분 애노테이션이 RUTIME정책인 것 같습니다.
런타임 그러니까 메모리에 올라왔을 때에도 애노테이션이 남아있어야하는 것들 입니다.
@Autowired
같은 걸로 적절하게 스프링 빈을 주입할 때 등록해놓은 빈들을 찾아야겠죠?
아마도 CLASS로 해놨으면 이게 스프링 빈으로써 메모리에 올라온 건지, 그냥 new로 생성해서 올라온 건지 모르지 않을까 했습니다.
즉 컴포넌트 스캔으로 찾아야하기 때문에 뭔가 표시가 남아있지 않을까 추측해봤습니다.
주의! 위 내용이 틀린 내용일 확률이 높습니다.
결론적으로 공부를 살짝 했습니다만, 정확하게 알아낸 내용은 아닙니다.
애노테이션의 라이프사이클에 대해서는 정확하지만 실제 예시와 합쳐져서 그런 이유로 동작하는지는 좀 더 검증이 필요하다고 생각합니다.
다소 추측이 난무하여 신빙성 없고(?) 실용적이지 않은(?) 내용을 여기까지 읽어주신 것에 감사드립니다...
참고 사이트
https://javabydeveloper.com/lombok-nonnull-annotation-examples/
http://www.javabyexamples.com/delombok-nonnull
https://stackoverflow.com/questions/38975073/retention-of-java-type-checker-annotations