# Retention

## Retention

이 문서에서는 Retention 설정 옵션에 따른 차이를 다룬다.

### @interface Retention

Library에서 Retention 코드를 찾아보면 다음과 같다. 편의상 주석은 제거하였다.

```java
@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의 코드는 다음과 같다.

```java
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 값은 다음과 같다.

* <mark style="color:blue;">SOURCE</mark> - 컴파일러에 의해 삭제되는 Annotation
* <mark style="color:blue;">CLASS</mark> - 컴파일러에 의해 클래스파일에 기록되지만 런타임 시 VM에서는 관리하지 않음
* <mark style="color:blue;">RUNTIME</mark> - 컴파일러에 의해 클래스파일에 기록되고 VM에서 관리하여 실행 중 읽을 수 있음

### RetentionPolicy.SOURCE

RetentionPolicy.SOURCE로 설정된 Annotation은 컴파일과 동시에 사라진다. 따라서 다음 용도로 사용이 가능하다.

1. 코드상에 단순한 표식(Marking) 생성
2. 컴파일 시 특정 코드로 치환되도록 자동화 구현

#### 코드상에 단순한 표식 설정   &#x20;

예를 들어 테스트가 진행중인 메소드에 다음과 같이 표시를 남겨 정보를 줄 수 있다.

먼저 Annotation을 다음과 같이 생성한다.

```java
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface TestInProgress {
	String tester();
	String last() default "today";
}
```

그리고 특정 메소드에 Annotation을 작성하고 옵션으로 정보를 설정한다.

```java
public class TestFunction {
	@TestInProgress(tester = "Hacademy")
	public static void print() {
		System.out.println("Hello world!");
	}
}
```

생성된 class 파일을 찾아서 디컴파일하면 다음과 같이 표시된다.

```java
// 
// Decompiled by Procyon v0.5.36
// 

package com.hacademy.annotation;

public class TestFunction
{
    public static void print() {
        System.out.println("Hello world!");
    }
}
```

<mark style="color:blue;">@TestInProgress</mark> Annotation이 사라진 것을 확인할 수 있다.

#### 컴파일 시 특정 코드로 치환되도록 자동화 구현

대표적인 라이브러리로 Lombok이 있다. Lombok에서 사용하는 @Getter @Setter @ToString @EqualsAndHashCode 등이 해당된다.

{% embed url="<https://projectlombok.org>" %}

{% embed url="<https://projectlombok.org/videos/lombok.ogv>" %}
lombok 공식 홈페이지에 있는 소개 비디오 영상
{% endembed %}

Lombok을 이용하여 다음과 같이 클래스를 하나 만든다.

{% code title="Book.java" %}

```java
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;
}
```

{% endcode %}

이 클래스에는 Lombok에서 제공하는 Annotation 4종류를 사용하였다.

* <mark style="color:blue;">@Data</mark> - Getter/Setter, ToString, EqualsAndHashCode 생성
* <mark style="color:blue;">@NoArgsConstructor</mark> - 기본 생성자 생성
* <mark style="color:blue;">@AllArgsConstructor</mark> - 모든 필드 초기화 생성자 생성
* <mark style="color:blue;">@Builder</mark> - 빌더 패턴을 위한 빌더 클래스 생성

디컴파일러를 이용하여 생성된 클래스를 살펴보면 다음과 같다.

{% embed url="<http://www.javadecompilers.com>" %}

{% code title="Book.class" %}

```java
// 
// 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;
    }
}
```

{% endcode %}

생성된 내부에 Java에서 작성한 적이 없는 코드들이 추가되어 있는 것을 확인할 수 있다. 또한 <mark style="color:blue;">@Builder</mark> 로 인하여 빌더 클래스가 하나 더 생긴것을 확인할 수 있다.

{% code title="Book$BookBuilder.class" %}

```java
// 
// 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 + ")";
    }
}
```

{% endcode %}

사용한 Lombok의 Annotation은 제거된 것을 확인할 수 있다. 각각의 Annotation을 보면 Retention 설정이 <mark style="color:blue;">RetentionPolicy.SOURCE</mark>로 되어 있는 것을 확인할 수 있다.

{% code title="Data.class" %}

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
    //(내용 생략)
}
```

{% endcode %}

{% code title="NoArgsConstructor.class" %}

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
    //(내용 생략) 
}
```

{% endcode %}

{% code title="AllArgsConstructor.class" %}

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {
   //(내용 생략) 
}
```

{% endcode %}

{% code title="Builder.class" %}

```java
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
   //(내용 생략) 
}
```

{% endcode %}

해당 내용들을 직접 적용하려면 <mark style="color:blue;">annotation processor</mark> 등의 처리가 필요하다.

### RetentionPolicy.CLASS

`RetentionPolicy.CLASS`로 설정된 Annotation은 컴파일 이후의 바이트 코드까지 유지되지만 런타임 시점까지 반드시 유지할 필요는 없다. 따라서 RetentionPolicy.SOURCE와 RetentionPolicy.RUNTIME의 중간 정도로 이해할 수 있다. 보기에 따라서 상당히 애매해 보일 수 있지만 자바 애플리케이션을 만들 때 다수의 jar 파일을 사용하고, 이 jar 파일에는 class 파일만 들어있다는 것을 생각해보면 <mark style="color:red;">**배포된 파일에는 포함되어야 하지만 실행 시에는 포함될 필요가 없는 경우 사용하는 형태**</mark>라고 볼 수 있다.

### RetentionPolicy.RUNTIME

`RetentionPolicy.RUNTIME`으로 설정된 Annotation은 VM에서 런타임 시점까지 유지한다. 런타임 시점까지 유지된다는 것은 코드를 통해 Annotation의 유무와 내부에 설정된 옵션을 읽을 수 있다는 의미이다. [Java Reflection](/base-language/java/java-advanced/java-reflection.md)과 같은 기술을 사용하여 Annotation을 해석하고, 이에 따라 다른 작업을 수행할 수 있다.

다음 Book 클래스를 통해 좀 더 자세히 살펴본다.

{% code title="Book.java" %}

```java
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 + "]";
	}
}
```

{% endcode %}

Book 클래스에는 @TestEntity 라는 Annotation이 설정되어 있다. @TestEntity의 코드는 다음과 같다.

```java
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을 이용하여 이를 알아낼 수 있다.

{% code title="AnnotationCheckApplication.java" %}

```java
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());
		}
	}
}

```

{% endcode %}

메소드는 Method, 생성자는 Constructor, 필드는 Field 클래스에 존재하는 annotation 반환 명령을 이용하여 특정 Annotation이 존재하는지 확인할 수 있고, 필요하다면 모든 Annotation 목록을 반환하도록 만들 수 있다.

* getAnnotation(Class) - 상속된 Annotation을 <mark style="color:green;">포함</mark>하여 특정 Annotation의 유무 조회
* getDeclaredAnnotation(Class) - 상속된 Annotation을 <mark style="color:red;">제외</mark>하고 특정 Annotation의 유무 조회
* getAnnotations() - 상속된 Annotation을 <mark style="color:green;">포함</mark>하여 목록 조회
* getDeclaredAnnotations() - 상속된 Annotation을 <mark style="color:red;">제외</mark>하고 목록 조회


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sysout.co.kr/base-language/java/java-advanced/annotation-type/retention.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
