이번 글에서는 나만의 어노테이션을 만드는 방법에 대해서 알아보겠다.
바로 시작한다.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SampleAnnotation {}
기본적으로 Annotaion은 이러한 구조를 갖게 된다.
Retention과 Target을 설정해줘야하는데, 각가에 대해서 좀 더 자세히 알아보자.
Retention은 '유지'라는 뜻의 영단어 인데, 이름처럼 @Retention
은 해당 어노테이션을 언제까지 유지할 것인지를 정의하는 부분이다.
Java로 작성한 코드를 실행하면, 바이트 코드로 컴파일된 후, JVM위에서 돌아가게 되는데, 이러한 라이프 사이클 위에서 언제까지 유지시킬지를 정의하는 것이라고 볼 수 있다.
그렇다면, 어떠한 종류의 옵션들이 있는지 간단히 알아보자.
@Retention(RetentionPolicy.SOURCE)
: 컴파일러에 의해서 제거가 된다. 즉, 컴파일 전까지만 유지되는 어노테이션이 필요할 경우 사용할 수 있는 옵션이다.@Retention(RetentionPolicy.CLASS)
: 컴파일러에 의해 .class
파일에 기록되지만, JVM은 어노테이션을 유지하지 않는다.@Retention(RetentionPolicy.RUNTIME)
: 컴파일러에 의해 .class
파일에 기록되고, JVM에 의해서 어노테이션이 유지된다. 즉, Java로 작성한 코드가 돌아가는 동안엔 계속 유지된다는 의미이다.@Target
어노테이션은 이름에서도 유추할 수 있듯이, 어노테이션이 적용될 수 있는 대상을 정의할 수 있는 어노테이션이다.
대상을 지정하는 옵션은 꽤 많은데, 하나 하나 알아보자.
@Target({ElementType.TYPE})
: 클래스, 인터페이서, Enum 또는 Record에 붙일 수 있다는 의미이다.@Target({ElementType.FIELD})
: 변수에 붙일 수 있다는 의미이다.@Target({ElementType.METHOD})
: 메서드에 붙일 수 있다는 의미이다.@Target({ElementType.PARAMETER})
: 메서드의 파라미터에 붙일 수 있다는 의미이다. 클라이언트에서 전달 받는 API의 파라미터들에 자주 사용된다.@Target({ElementType.CONSTRUCTOR})
: 생성자에 붙일 수 있다는 의미이다.@Target({ElementType.LOCAL_VARIABLE})
: 변수 중, 지역 변수에만 붙일 수 있다는 의미이다.@Target({ElementType.ANNOTATION_TYPE})
: 어노테이션에 붙일 수 있다는 의미이다.@Target({ElementType.PACKAGE})
: 패키지에 붙일 수 있는 것을 의미하는데, 기능적인 역할은 아니고 패키지 정보를 기록하는 역할을 한다. (관련 문서)@Target({ElementType.TYPE_PARAMETER})
: 타입 변수에만 붙일 수 있는 것을 의미한다. 타입 변수는 제네릭에 선언하는 타입같은 것들을 의미한다.@Target({ElementType.TYPE_USE})
: 타입 변수 뿐만 아니라 모든 타입에 사용할 수 있다는 의미의 옵션이다.@Target({ElementType.RECORD_COMPONENT})
: java14에서 추가된 record에 사용할 수 있는 어노테이션이라는 의미이다.@Target({ElementType.MODULE})
: 모듈에 사용할 수 있는 어노테이션을 의미하는데, 정확한 사용법은 사실 잘.. 모르겠다. 😅사용 가능한 옵션들을 모두 열거하다보니, 내용이 좀 길어졌다. 아무튼 내가 만들고자 하는 어노테이션의 목적에 맞게 위 옵션들을 적절히 선택해주면 된다.
이름을 변수로 받는 HelloWorld 어노테이션을 만들어보겠다.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface HelloWorldAnnotation {
String name() default "The name";
}
이렇게 어노테이션을 만들어주고, 잘 동작하는지 한번 확인해보자.
아래와 같이 테스트 코드를 만들고 실행해보자.
package com.example.helloboot.test;
import com.example.config.HelloWorldAnnotation;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.lang.annotation.Annotation;
public class HelloWorldAnnotationTest {
@HelloWorldAnnotation(name = "이순신")
class HelloWorld {}
@Test
@DisplayName("HelloWorld테스트")
public void HelloWorldTest() throws Exception {
Annotation[] annotations = HelloWorld.class.getAnnotations();
for (Annotation annotation : annotations) {
HelloWorldAnnotation helloWorldAnnotation = (HelloWorldAnnotation) annotation;
System.out.println("helloWorldAnnotation = " + helloWorldAnnotation.name());
}
}
}
단순하게, name에 어떤 값을 입력해보고, 그 값이 잘 입력되었는지 확인해보는 코드이다.
위 코드를 실행해보면 이렇게 내가 입력한 '이순신'이 잘 출력되는 것을 볼 수 있다.
어노테이션은 태생적으로 자체적으로 기능을 가질 수 없고, 메타 정보를 제공하는 역할을 한다.
즉, 나만의 어노테이션을 만들고 기능을 구현하고 싶다면, 이를 활용하는 코드 또한 작성해주어야한다.
구현하기 나름이지만, AOP 또는 Java Reflection같은 기술을 통해서 기능을 구현하곤한다. (Java Reflection)
기능 구현까지 글을 쓰면 내용이 너무 많아질 것 같아서.. 일단은 한번 끊고 가겠다 😅
AOP까지 사용해서 구현하면 글이 너무 길어질 것 같아서, 커스텀 어노테이션을 연동한 기능 구현은 스킵했다.
이 부분은 빠른 시일내에 글을 써보도록 하겠다. ㅎㅎ
어노테이션이 어떤 것이고, 어떻게 만들 수 있고, 어떻게 활용가능한지 알아보는 것으로 이번 글은 마치도록 하겠다. 🙏