본문 바로가기

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/

http://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

반응형
  • 박종훈 2021.03.25 10:56

    안녕하세요, DevBlog 구독 중인데 글이 흥미로워보여서 들어왔습니다^^

    - SOURCE: 소스코드까지만 유지 (즉, 컴파일 과정에서 어노테이션 정보는 사라짐)
    - CLASS: 클래스파일 까지만 유지 (런타임시 유지안됨)
    - RUNTIME: 런타임 시점까지 유지 (Reflection API 로 어노테이션 정보 조회 가능)

    원할한 이해를 위해.. SOURCE -> RUNTIME -> CLASS 순으로 설명드릴께요.

    SOURCE 정책
    Getter / Setter 같은 경우 롬복이 바이트 '코드를 생성'해서 넣어주는 것이기 때문에, 굳이 바이트코드에 어노테이션 정보가 들어갈 필요가 없습니다. (왜냐하면 롬복이 코드를 생성해주니까..) 이 점에 대해서는 글에도 잘 설명되어 있네요^^

    RUNTIME 정책
    런타임에 어노테이션 정보를 뽑아 쓸수 있다는 의미입니다. 즉, Reflection API 등을 사용하여 어노테이션 정보를 알수가 있다는 의미입니다. 스프링 학습을 하시는 것 같아서 스프링을 예로 들자면, @Controller, @Service, @Autowired 등이 있습니다. 스프링이 올라오는 실행 중인 시점에 컴포넌트 스캔이 가능해야하기 때문에 RUNTIME 정책이 필요합니다. (스프링도 내부적으로 Reflection 등을 활용하여 어노테이션이 붙은 놈들만 가져옵니다.)

    CLASS 정책
    그러면, CLASS 정책은 왜 필요한지 궁금하실거에요. "아니 Reflection 같은걸로 정보를 얻을수도 없으면서 왜 필요한거지?" 말이죠 ㅎㅎ
    그런데 이미 '참고사이트'에 첨부해주신 스택오버플로우('retention-of-java-type-checker-annotations') 글에 설명이 되어있습니다. 인텔리제이를 써보셨다면, @NonNull 등이 붙어있는 경우 null 값을 넣게되면 노랑색 경고로 알려줍니다.
    "아니 그러면 SOURCE로 해도 될거 같은데?" 싶으실텐데요, 중요한점은 Maven/Gradle로 다운받은 라이브러리와 같이 jar 파일에는 소스가 포함되어있지 않다는 점입니다. class 파일만 포함되어있죠 (Download Sources 옵션은 논외로 할께요)

    즉, class 파일만 존재하는 라이브러리 같은 경우에도 타입체커, IDE 부가기능 등을 사용할수 있으려면 CLASS 정책이 필요하게 됩니다. SOURCE 정책으로 사용한다면 컴파일된 라이브러리의 jar 파일에는 어노테이션 정보가 남아있지 않기 때문이죠.
    그외에도 클래스로딩시 무언가를 하고 싶은 경우에도 사용될수도 있고요^^

    도움 되시길 바랍니다.

    • 그저 빛...

      박종훈님 덕분에 큰 도움되었습니다. 감사합니다!

      그냥 지나치실 수도 있었음에도 불구하고 적어주신 장문의 친절한 댓글이 저 뿐만 아니라 많은 개발자들에게 도움이 될 것 같습니다.
      (믿어 의심치 않습니다~)

      ".class 파일로만 존재하는 경우"는 생각지도 못했네요.

    • BlogIcon 목하 2021.07.13 21:41

      CLASS 는 이해 안갔는데 써주신 댓글보고 이해하고갑니다! 감사합니다(_ _)

    • BlogIcon wlsgmdchemd@naver.coms 2021.10.31 01:37

      킹종훈 당신은 도덕책...

  • Favicon of https://team00csduck.tistory.com BlogIcon 컴공오리 2021.03.27 02:44 신고

    지나칠 수 있는데 흥미로운 주제로 글 써주신 쓰니님과 지나칠 수 있는데 멋있는 댓글 써주신 박종훈님 덕분에 도움이 되었습니다. 그냥 읽기 아쉬워서 광고도 2번 클릭했습니다. 좋은 글 잘 읽고 갑니다!

  • Favicon of https://yuja-kong.tistory.com BlogIcon DEV유자 2021.04.03 16:13 신고

    잘 봤습니다. 아직 이해가 어렵긴 하지만 더 공부하다보면 이해할 날이오겠죠?ㅎㅎ..
    블로그 전반적으로 읽으면 좋은 글이많네요! 자주 놀러올게요 ^^ !!