Retention
이 문서에서는 Retention 설정 옵션에 따른 차이를 다룬다.
@interface Retention
Library에서 Retention 코드를 찾아보면 다음과 같다. 편의상 주석은 제거하였다.
Copy @ Documented
@ Retention ( RetentionPolicy . RUNTIME )
@ Target ( ElementType . ANNOTATION_TYPE )
public @ interface Retention {
RetentionPolicy value() ;
}
@Documented
- 작성된 Annotation이 Javadoc에 문서화 됨을 표시
@Target
- 이 Annotation이 다른 Annotation type에 작성될 수 있음을 표시
Annotation 내부에는 RetentionPolicy라는 Enum 값을 저장할 수 있는 value 속성이 존재한다.
enum RetentionPolicy
RetentionPolicy의 코드는 다음과 같다.
Copy public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE ,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS ,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
설정 가능한 RetentionPolicy 값은 다음과 같다.
SOURCE - 컴파일러에 의해 삭제되는 Annotation
CLASS - 컴파일러에 의해 클래스파일에 기록되지만 런타임 시 VM에서는 관리하지 않음
RUNTIME - 컴파일러에 의해 클래스파일에 기록되고 VM에서 관리하여 실행 중 읽을 수 있음
RetentionPolicy.SOURCE
RetentionPolicy.SOURCE로 설정된 Annotation은 컴파일과 동시에 사라진다. 따라서 다음 용도로 사용이 가능하다.
컴파일 시 특정 코드로 치환되도록 자동화 구현
코드상에 단순한 표식 설정
예를 들어 테스트가 진행중인 메소드에 다음과 같이 표시를 남겨 정보를 줄 수 있다.
먼저 Annotation을 다음과 같이 생성한다.
Copy @ Retention ( RetentionPolicy . SOURCE )
@ Target ( ElementType . METHOD )
public @ interface TestInProgress {
String tester() ;
String last() default "today" ;
}
그리고 특정 메소드에 Annotation을 작성하고 옵션으로 정보를 설정한다.
Copy public class TestFunction {
@ TestInProgress (tester = "Hacademy" )
public static void print () {
System . out . println ( "Hello world!" );
}
}
생성된 class 파일을 찾아서 디컴파일하면 다음과 같이 표시된다.
Copy //
// Decompiled by Procyon v0.5.36
//
package com . hacademy . annotation ;
public class TestFunction
{
public static void print () {
System . out . println ( "Hello world!" );
}
}
@TestInProgress Annotation이 사라진 것을 확인할 수 있다.
컴파일 시 특정 코드로 치환되도록 자동화 구현
대표적인 라이브러리로 Lombok이 있다. Lombok에서 사용하는 @Getter @Setter @ToString @EqualsAndHashCode 등이 해당된다.
Lombok을 이용하여 다음과 같이 클래스를 하나 만든다.
Copy package com . hacademy . annotation ;
import lombok . AllArgsConstructor ;
import lombok . Builder ;
import lombok . Data ;
import lombok . NoArgsConstructor ;
@ Data @ NoArgsConstructor @ AllArgsConstructor @ Builder
public class Book {
private String name;
private int price;
}
이 클래스에는 Lombok에서 제공하는 Annotation 4종류를 사용하였다.
@Data - Getter/Setter, ToString, EqualsAndHashCode 생성
@NoArgsConstructor - 기본 생성자 생성
@AllArgsConstructor - 모든 필드 초기화 생성자 생성
@Builder - 빌더 패턴을 위한 빌더 클래스 생성
디컴파일러를 이용하여 생성된 클래스를 살펴보면 다음과 같다.
Copy //
// Decompiled by Procyon v0.5.36
//
package com . hacademy . annotation ;
public class Book
{
private String name;
private int price;
public static Book . BookBuilder builder () {
return new Book . BookBuilder ();
}
public String getName () {
return this . name ;
}
public int getPrice () {
return this . price ;
}
public void setName ( final String name) {
this . name = name;
}
public void setPrice ( final int price) {
this . price = price;
}
@ Override
public boolean equals ( final Object o) {
if (o == this ) {
return true ;
}
if ( ! (o instanceof Book)) {
return false ;
}
final Book other = (Book)o;
if ( ! other . canEqual ( this )) {
return false ;
}
if ( this . getPrice () != other . getPrice ()) {
return false ;
}
final Object this$name = this . getName ();
final Object other$name = other . getName ();
if ( this $name == null ) {
if (other$name == null ) {
return true ;
}
}
else if ( this $name . equals (other$name)) {
return true ;
}
return false ;
}
protected boolean canEqual ( final Object other) {
return other instanceof Book;
}
@ Override
public int hashCode () {
final int PRIME = 59 ;
int result = 1 ;
result = result * 59 + this . getPrice ();
final Object $name = this . getName ();
result = result * 59 + (($name == null ) ? 43 : $name . hashCode ());
return result;
}
@ Override
public String toString () {
return "Book(name=" + this . getName () + ", price=" + this . getPrice () + ")" ;
}
public Book () {
}
public Book ( final String name , final int price) {
this . name = name;
this . price = price;
}
}
생성된 내부에 Java에서 작성한 적이 없는 코드들이 추가되어 있는 것을 확인할 수 있다. 또한 @Builder 로 인하여 빌더 클래스가 하나 더 생긴것을 확인할 수 있다.
Copy //
// Decompiled by Procyon v0.5.36
//
package com . hacademy . annotation ;
public static class BookBuilder
{
private String name;
private int price;
BookBuilder () {
}
public BookBuilder name ( final String name) {
this . name = name;
return this ;
}
public BookBuilder price ( final int price) {
this . price = price;
return this ;
}
public Book build () {
return new Book( this . name , this . price ) ;
}
@ Override
public String toString () {
return "Book.BookBuilder(name=" + this . name + ", price=" + this . price + ")" ;
}
}
사용한 Lombok의 Annotation은 제거된 것을 확인할 수 있다. 각각의 Annotation을 보면 Retention 설정이 RetentionPolicy.SOURCE 로 되어 있는 것을 확인할 수 있다.
Copy @ Target ( ElementType . TYPE )
@ Retention ( RetentionPolicy . SOURCE )
public @ interface Data {
//(내용 생략)
}
Copy @ Target ( ElementType . TYPE )
@ Retention ( RetentionPolicy . SOURCE )
public @ interface NoArgsConstructor {
//(내용 생략)
}
Copy @ Target ( ElementType . TYPE )
@ Retention ( RetentionPolicy . SOURCE )
public @ interface AllArgsConstructor {
//(내용 생략)
}
Copy @ Target ({TYPE , METHOD , CONSTRUCTOR})
@ Retention (SOURCE)
public @ interface Builder {
//(내용 생략)
}
해당 내용들을 직접 적용하려면 annotation processor 등의 처리가 필요하다.
RetentionPolicy.CLASS
RetentionPolicy.CLASS
로 설정된 Annotation은 컴파일 이후의 바이트 코드까지 유지되지만 런타임 시점까지 반드시 유지할 필요는 없다. 따라서 RetentionPolicy.SOURCE와 RetentionPolicy.RUNTIME의 중간 정도로 이해할 수 있다. 보기에 따라서 상당히 애매해 보일 수 있지만 자바 애플리케이션을 만들 때 다수의 jar 파일을 사용하고, 이 jar 파일에는 class 파일만 들어있다는 것을 생각해보면 배포된 파일에는 포함되어야 하지만 실행 시에는 포함될 필요가 없는 경우 사용하는 형태 라고 볼 수 있다.
RetentionPolicy.RUNTIME
RetentionPolicy.RUNTIME
으로 설정된 Annotation은 VM에서 런타임 시점까지 유지한다. 런타임 시점까지 유지된다는 것은 코드를 통해 Annotation의 유무와 내부에 설정된 옵션을 읽을 수 있다는 의미이다. Java Reflection 과 같은 기술을 사용하여 Annotation을 해석하고, 이에 따라 다른 작업을 수행할 수 있다.
다음 Book 클래스를 통해 좀 더 자세히 살펴본다.
Copy package com . hacademy . annotation ;
@ TestEntity (author = "hacdemy" , date = "2022-03-03" , comment = "테스트를 위한 Annotation" )
public class Item {
private String name;
private int price;
public void setName ( String name) {
this . name = name;
}
public void setPrice ( int price) {
this . price = price;
}
public String getName () {
return name;
}
public int getPrice () {
return price;
}
@ Override
public String toString () {
return "Item [name=" + name + ", price=" + price + "]" ;
}
}
Book 클래스에는 @TestEntity 라는 Annotation이 설정되어 있다. @TestEntity의 코드는 다음과 같다.
Copy package com . hacademy . annotation ;
import java . lang . annotation . ElementType ;
import java . lang . annotation . Retention ;
import java . lang . annotation . RetentionPolicy ;
import java . lang . annotation . Target ;
@ Target ( ElementType . TYPE )
@ Retention ( RetentionPolicy . RUNTIME )
public @ interface TestEntity {
String author() default "" ;
String date() default "N/A" ;
String comment() default "" ;
}
Retention 설정이 RetentionPolicy.RUNTIME으로 되어 있기 때문에 실행 중에도 Annotation이 유지되며 Java Reflection을 이용하여 이를 알아낼 수 있다.
AnnotationCheckApplication.java
Copy package com . hacademy . annotation ;
import java . lang . annotation . Annotation ;
public class AnnotationCheckApplication {
public static void main ( String [] args) throws ClassNotFoundException {
Class < ? > c = Class . forName ( "com.hacademy.annotation.Item" );
Annotation annotation = c . getDeclaredAnnotation ( TestEntity . class );
if (annotation == null ) {
System . out . println ( "일반 클래스" );
}
else {
System . out . println ( "테스트 진행중인 클래스" );
TestEntity test = (TestEntity)annotation;
System . out . println ( test . author ());
System . out . println ( test . date ());
System . out . println ( test . comment ());
}
}
}
메소드는 Method, 생성자는 Constructor, 필드는 Field 클래스에 존재하는 annotation 반환 명령을 이용하여 특정 Annotation이 존재하는지 확인할 수 있고, 필요하다면 모든 Annotation 목록을 반환하도록 만들 수 있다.
getAnnotation(Class) - 상속된 Annotation을 포함 하여 특정 Annotation의 유무 조회
getDeclaredAnnotation(Class) - 상속된 Annotation을 제외 하고 특정 Annotation의 유무 조회
getAnnotations() - 상속된 Annotation을 포함 하여 목록 조회
getDeclaredAnnotations() - 상속된 Annotation을 제외 하고 목록 조회