Retention
Retention
이 문서에서는 Retention 설정 옵션에 따른 차이를 다룬다.
@interface Retention
Library에서 Retention 코드를 찾아보면 다음과 같다. 편의상 주석은 제거하였다.
@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의 코드는 다음과 같다.
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은 컴파일과 동시에 사라진다. 따라서 다음 용도로 사용이 가능하다.
코드상에 단순한 표식(Marking) 생성
컴파일 시 특정 코드로 치환되도록 자동화 구현
코드상에 단순한 표식 설정
예를 들어 테스트가 진행중인 메소드에 다음과 같이 표시를 남겨 정보를 줄 수 있다.
먼저 Annotation을 다음과 같이 생성한다.
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface TestInProgress {
String tester();
String last() default "today";
}
그리고 특정 메소드에 Annotation을 작성하고 옵션으로 정보를 설정한다.
public class TestFunction {
@TestInProgress(tester = "Hacademy")
public static void print() {
System.out.println("Hello world!");
}
}
생성된 class 파일을 찾아서 디컴파일하면 다음과 같이 표시된다.
//
// 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을 이용하여 다음과 같이 클래스를 하나 만든다.
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 - 빌더 패턴을 위한 빌더 클래스 생성
디컴파일러를 이용하여 생성된 클래스를 살펴보면 다음과 같다.
//
// 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 로 인하여 빌더 클래스가 하나 더 생긴것을 확인할 수 있다.
//
// 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로 되어 있는 것을 확인할 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
//(내용 생략)
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
//(내용 생략)
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {
//(내용 생략)
}
@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 클래스를 통해 좀 더 자세히 살펴본다.
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의 코드는 다음과 같다.
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을 이용하여 이를 알아낼 수 있다.
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을 제외하고 목록 조회
Last updated