본문 바로가기

Spring/Spring

아무 관심 없던 @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/

https://www.javabyexamples.com/delombok-nonnull

https://stackoverflow.com/questions/38975073/retention-of-java-type-checker-annotations

https://stackoverflow.com/questions/3107970/how-do-different-retention-policies-affect-my-annotations

반응형