기록하자..
Annotation | 백기선님 LIVE-STUDY 본문
애노테이션
학습할 것
- 애노테이션 정의하는 방법
- @retention
- @target
- @documented
- 애노테이션 프로세서
애노테이션이란?
애노테이션은 메타데이터로 볼 수 있다.
메타데이터는 애플리케이션이 처리해야 할 데이터가 아니라 컴파일 과정에서 코드를 어떻게 컴파일하고 처리할 것인지를 알려주는 정보이다.
애노테이션은 세 가지 용도로 사용된다.
- 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공
- 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보를 제공
- 런타임 시간에 특정 기능을 실행하도록 정보를 제공
애노테이션은 코드에 넣는 주석이기 때문에 실행되는 것이 아니라고 생각해야 한다.
애노테이션에 동적으로 실행되는 코드는 들어가지 않는다.
애노테이션 정의하는 방법
애노테이션을 정의하는 방법은 다음과 같다.
public @interface AnnotationName {
}
인터페이스를 정의하는 것과 유사한데, 위와 같이 @interface를 사용해서 애노테이션을 정의한다. 이렇게 정의한 애노테이션은 다음과 같이 사용한다.
@AnnotationName
애노테이션은 엘리면트(element)를 멤버로 가질 수 있다. 각 엘리먼트는 타입과 이름으로 구성되며, 디폴트 값을 가질 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String elementName();
int elementValue() default 5;
}
@MyAnnotation(
elementName = "값",
elementValue = 3
)
public void method() {
// ...
}
메타 애노테이션
메타 애노테이션은 애노테이션을 위한 애노테이션으로 애노테이션을 정의할 때 사용한다. 애노테이션의 적용대상이나 유지 정책을 지정하는데 사용된다.
@Retention
애노테이션이 유지되는 범위를 지정하는데 사용한다.
- 소스상에만 유지
- 컴파일된 클래스까지 유지
- 런타임 시에도 유지
애노테이션 유지 정책은 java.lang.annotation.RetentionPolicy 에 열거 상수로 정의되어 있다.
- SOURCE
- 소스상에서만 애노테이션 정보를 유지한다. 소스 코드를 분석할 때만 의미가 있고, 바이트코드 파일에는 정보가 남지 않는다.
- CLASS
- 바이트코드 파일까지 애노테이션 정보를 유지한다. 리플렉션을 사용해서 애노테이션 정보를 얻을 수 없다.
- RUNTIME
- 바이트코드 파일까지 애노티에션 정보를 유지하면서 리플렉션을 통해서 런타임 시간에 애노테이션 정보를 얻을 수 있다.
@Target
애노테이션을 적용할 수 있는 대상을 지정하는데 사용한다.
애노테이션을 적용할 수 있는 대상은 java.lang.annotation.ElementType 에 열거 상수로 정의되어 있다.
- TYPE
- 클래스, 인터페이스, 열거 타입
- ANNOTATION_TYPE
- 애노테이션
- FIELD
- 필드
- CONSTRUCTOR
- 생성자
- METHOD
- 메서드
- LOCAL_VARIABLE
- 로컬변수
- PACKAGE
- 패키지
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface MyAnnotation {
}
다음과 같이 애노테이션을 선언할 경우,
@MyAnnotation
public class AnnotationEx {
@MyAnnotation
private String elem;
@MyAnnotation
public void method() {
// ...
}
}
위와 같이 애노테이션을 적용할 수 있다.
런타임 시 애노테이션 이용하기
애노테이션의 유지정책을 RUNTIME으로 설정할 셩우 리플렉션을 이용해서 애노테이션의 적용 여부와 엘리먼트 값을 읽고 적절히 처리할 수 있다.
다음과 같이 애노테이션을 선언했다.
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.METHOD)
public @interface MyAnnotation {
}
이후 이 애노테이션을 이용해서 메서드 실행시켜보자.
import java.lang.reflect.Method;
public class AnnotationEx {
@MyAnnotation
public void method1() {
System.out.println("method1 invoked");
}
@MyAnnotation
private void method2() {
System.out.println("method2 invoked");
}
private String method3() {
System.out.println("method3 invoked");
return "METHOD3";
}
public static void main(String[] args) {
AnnotationEx ex = new AnnotationEx();
Method[] declaredMethods = AnnotationEx.class.getDeclaredMethods();
for (Method method : declaredMethods) {
if (method.isAnnotationPresent(MyAnnotation.class)) {
System.out.println("[" + method.getName() + "]");
try {
method.invoke(new AnnotationEx());
} catch (Exception e) { }
}
}
}
}
[method2]
method2 invoked
[method1]
method1 invoked
@Documented
애노테이션 정보가 javadoc으로 작성된 문서에 포함되도록 하는 애노테이션이다. 이는 직접 javadoc을 작성할 수 있다는 의미이다.
HTML 형식이기 때문에 다른 API를 하이퍼링크를 통한 접근이 가능하다.
애노테이션 프로세서
annotation processor란 무엇일까?
컴파일 타임에 annotation을 스캔하고 다루기 위해 javac에서 확장해서 사용하는 도구라고 생각할 수 있겠다.
특정 annotation에 대한 annotation processor는 java 코드를 입력으로 사용하고 파일(일반적으로 .java)을 출력으로 생성한다. 이것이 의미하는 바는 java 코드를 생성할 수 있다는 것이고 이 코드가 .java 파일에 저장된다는 것이라고 생각할 수 있다. 그래서 우리가 메서드를 추가하기 위해서 class 파일을 수정할 필요가 없다는 것을 말한다.
쉽게 생각하면 annotation processor는 컴파일러가 컴파일을 수행할 때 끼어들어서 특정한 annotaiton이 붙어있는 코드들을 읽어서 또 다른 소스코드를 생성하도록 도와주는 것이다.
물론 기존의 코드까지도 바꾸어버릴 수 있는 lombok 같은 것도 존재한다. 원래 annotation processor는 원래 코드를 수정하지 못하도록 되어 있는데, lombok의 target 폴더를 보면 코드가 수정되어있는 것을 확인할 수 있다. 이래서 몇몇 개발자 분들은 lombok은 해킹과 같다라고 하기도 한다고..
그래서 lombok의 대안책으로 AutoValue, Immutable 등을 사용한다고 한다. (이것들은 코드를 수정하지 않는다.)
annotation processor API
annotation processor는 여러 round에 거쳐서 발생한다. 각 라운드는 컴파일러가 소스파일에서 annotation을 검색을 하고 검색한 annotation에 알맞은 annotation processor를 선택하는 것부터 시작을 한다. 각 annotation processor는 그것과 일치하는 annotation이 발견되었을 때 호출된다. 그리고 각 라운드에서 프로세서는 이전 라운드에서 생성된 클래스나 소스파일에서 찾은 annotation 들의 집합을 처리하도록 요청받는다.
이 annotation processor API는 javax.annotation.processing 패키지에 있다. 우리가 구현하고자 하는 annotation processor는 보통 AbstractProcessor 클래스를 상속받아서 사용한다. 이 AbstractProcessor 클래스는 Processor 인터페이스를 구현하고 있는데, Processor 인터페이스의 메서드들을 거의 구현하고 있어 사용하기 편리하도록 해준다.
AbstractProcessor
AbstractProcessor를 상속하는 클래스들은 다음과 같은 구조를 따른다.
public class BuilderProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
init : ProcessingEnvironment로 프로세서를 초기화한다.
getSupportedAnnotationType : 이 프로세서에 의해 처리되는 annotation이름들을 반환해준다. 여기서 반환되는 타입인 Set은 annotation processor에 의해 처리되는 annotaition 타입의 완전한 이름을 포함해야 한다.
getSupportedSourceVersion : 이 annotation processor가 지원하는 최신 자바 버전을 지원한다. 보통 SourceVersion.latestSupported() 를 사용한다.
process : 이전 라운드에서 시작된 TypeElement의 annotation 집합을 처리하고 이러한 주석 유형이 이 프로세서에 의해 처리되었는지 여부를 반환한다. 리턴 값이 true이면 여기서 이 annotation 타입을 처리한 것이고, 이는 다음 프로세서에게 이 annotation을 처리하지 말라고 하는 것이다.
annotation processor의 등록
annotation processor를 등록하기 위해서는 .jar 을 컴파일러의 class path에 추가해야 한다. 이를 자동으로 선택하려면 META-INF/services 에 위치한 javax.annotation.processing.Processor 라는 파일도 패키징 해야 한다. 이 파일에는 프로세서의 풀 패키지 경로를 추가하면 된다.
Maven을 사용하여 이 jar 파일을 빌드 하려고 하면 오류가 발생한다. javax.annotation.processing.Processor 파일을 사용하려는 시점이 소스를 컴파일할 때가 아닌데, 이거를 소스를 컴파일하는 시점에 이 프로세서가 동작을 하려고 한다. 그런데 그 시점에는 아직 프로세서가 없어서 오류가 발생한다.
이를 해결하는 방법은 javax.annotation.processing.Processor 파일의 내용을 잠시 주석처리 해놓고 mvn clean install 을 실행한다. 이렇게 되면 이 파일을 사용하지 않고 컴파일한 후 jar 패키징을 수행한다. 이후 mvn install 을 실행하면 target 디렉토리를 비우지 않고 있는 것을 그대로 컴파일하기 때문에 오류가 발생하지 않는다.
더 좋은 방법은 @AutoSerivce 를 사용하면 된다. 이를 사용하면 이런 manifest 파일을 컴파일할 때 자동으로 생성해주기 때문에 위의 번거로운 과정을 수행하지 않아도 된다.
참고 자료
'자바' 카테고리의 다른 글
| 제네릭 | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
|---|---|
| I/O | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
| Enum | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
| 멀티스레드 프로그래밍 | 백기선님 LIVE-STUDY (0) | 2022.05.31 |
| 예외처리 | 백기선님 LIVE-STUDY (0) | 2022.05.31 |