학습한 내용을 정리한 포스팅입니다. 틀린 내용이 있다면 지적해주시면 감사하겠습니다.🙇🏻♀️
@
만 붙일 뿐 인터페이스 정의하는 방법과 같다.
public @interface JsonSerializable {
}
이후에 스코프와 타겟을 특정화하기 위해서 메타 어노테이션을 추가한다.@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface JsonSerializable {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonElement {
public String key() default "";
}
어노테이션은 메소드를 가질 수 있는데 이 때 이 메소드들을 요소(Element)라고 한다. 이 요소들은 아래의 규칙을 따라야 한다.@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Init {
}
위의 세 예제를 보면 알수 있듯이 @Target 어노테이션으로 적용되는 대상을 선언할 수 있다(메소드인지 필드인지 등등)
@retention
어노테이션이 유지되는 범위 (= 기간)을 지정하는데 사용된다. 유지 정책에는 아래와 같다.
SOURCE
: 소스 파일에만 존재하고 클래스 파일에는 존재하지 않음.CLASS
: 클래스 파일에 존재하지만 실행시에는 사용불가. 기본값RUNTIME
: 클래스 파일에 존재하고 실행시에도 사용 가능컴파일러를 직접 작성할 것이 아니면 SOURCE는 필요 없다. RUNTIME으로 할 시에는 리플렉션을 통해 클래스 파일에 저장된 어노테이션 정보를 읽어서 실행시에 처리한다.
Retention어노테이션이 없는 경우에는 RetentionPolicy.CLASS를 따른다.
→ 하지만 CLASS 같은 경우에는 클래스 파일에 어노테이션 정보를 저장할 수는 있으나 실행 시에는 해당 정보가 무시되어 어노테이션에 대한 정보를 얻을 수 없어 기본 정책임에도 불구하고 잘 사용되지 않는다.
@target
어노테이션이 적용되는 대상을 지정하는데 쓰인다. 적용대상의 종류는 아래와 같다.
@documented
어노테이션에 대한 정보가 javadoc으로 포함되도록 한다. 기본 어노테이션 중에 @Override
, @SuppressWarnings
를 제외하고는 모두 해당 어노테이션이 붙어 있다.
아래의 예시는 여기에 나와있는 예시를 보고 이해한 대로 다시 써보았습니다. 틀린 내용이 있다면 지적해주시면 감사하겠습니다
어노테이션 프로세싱은 컴파일 타임에 일어난다. 어노테이션 프로세싱은 컴파일 타임에 어노테이션들을 프로세싱하고 스캔하기 위한 javac의 도구라고 볼 수 있다. 어노테이션 프로세싱은 자바 5부터 사용가능했지만 유용한 API는 자바 6부터 사용 가능하게 됐다.
특정 어노테이션을 위한 어노테이션 프로세서는 인풋으로 자바 코드 (또는 컴파일된 바이트 코드)를 필요로 하고 생성된 파일들(주로 .java 파일)을 아웃풋으로 제공한다. 직접 작성된 여느 자바 소스 파일들 처럼 javac가 생성된 자바 파일을 컴파일한다.
실제 어노테이션 프로세서가 어떻게 사용되는지 간단한 예제를 아래에서 확인할 수 있다.
예제) 우리가 만든 @Test
어노테이션을 활용해서 테스트 시 테스트 클래스의 어떤 메소드들을 실행할지 런타임시 결정할 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
다음으로 AnnotatedMethod 클래스를 생성한다. test1() 메소드는 @Test
어노테이션을 적용해 런타임시에 실행되도록 할 것이고 test2() 메소드는 어떤 어노테이션도 붙이지 않을 것이다. (런타임시에 실행되지 않게 하기 위해)
public class AnnotatedMethods {
@Test
public void test1() {
System.out.println("This is the first test");
}
public void test2() {
System.out.println("This is the second test");
}
}
이제 AnnotatedMethod 클래스의 테스트를 만들어보자
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class TestAnnotatedMethods {
public static void main(String[] args) throws Exception {
Class<AnnotatedMethods> annotatedMethodsClass = AnnotatedMethods.class;
for (Method method : annotatedMethodsClass.getDeclaredMethods()) {
Annotation annotation = method.getAnnotation(Test.class);
Test test = (Test) annotation;
// If the annotation is not null
if (test != null) {
try {
method.invoke(annotatedMethodsClass
.getDeclaredConstructor()
.newInstance());
} catch (Throwable ex) {
System.out.println(ex.getCause());
}
}
}
}
}
getDeclaredMethod()을 통해서 AnnotatedMethod 클래스의 메소드들을 불러올 수 있다. 이후에 메소드들을 순회하면서 @Test
어노테이션을 가지고 있는지 확인한다. 마지막으로 @Test
어노테이션을 가지고 있는 메서드를 런타임시 실행한다.
test1() 메소드에만 @Test
어노테이션을 붙였으므로 test2()는 실행되지 않을 것이다.
실행 결과)
This is the first test
남궁성, 자바의 정석
baeldung.com/java-custom-annotation
http://hannesdorfmann.com/annotation-processing/annotationprocessing101/
https://reflectoring.io/java-annotation-processing/