
이번엔 어노테이션 동작원리에 대해 알아보자.
Annotation Processor라는 놈이 컴파일시점에 해당 어노테이션과 어노테이션이 붙은 놈들을 수집해서 byte code를 만들고, class 파일을 만든다.
이 과정에서 라운드는 수집(실행)되지 않은 Annotation Processor가 있는지 확인한 후 모두 수행할 때까지 해당 작업을 반복합니다.
그렇다면 반복한다는것은 무엇인가?
Oracle은 Processing의 반복 횟수를 라운드라고 하기로 했습니다. 한 라운드에서는 하나의 어노테이션을 통해 보일러 플레이트 코드를 만들고 나서 다시 Parser가 검사를 하는데 이때 Annotation이 중첩이 되어있는 경우 아직 동작하지 않은(보일러 플레이트 코드를 만들지 않은) 어노테이션이 있을 수 있기 때문입니다.
보일러 플레이트 코드 : 비슷한 형태로 반복되는 코드
코드로 더 찾아보았다.
AbstractProcessor.Class
public abstract boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv);
//다른 라이브러리 process 재정의하여 구현 예시
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return instance.process(annotations, roundEnv);
}
//1 어노테이션 만들기
@Target(ElementType.TYPE) //Annotation 범위는 Interface, Enum, Class만 하는것은 TYPE설정이다.
@Retention(RetentionPolicy.SOURCE) // Annotation 유지는 Annotation Processor의 컴파일 시점까지만 필요하기 때문에 Source Code까지만 유지합니다.
public @interface GetterImpl {
}
//2 어노테이션 프로세서 만들기
/**
*AutoService를 이용하면 resources에 있는 메니페스토 파일을 컴파일할 때 자동으로 생성해 준다. 의미는 이 클래스를 프로세서로 등록해달라는 의미이다.
* 그게 아니라면 resources-META-services 경로에 javax.annotation.processing.Processor 파일을 생성하여 class를 등록해야한다
*/
@AutoService(Processor.class)
public class GetterProcessor extends AbstractProcessor {
// 이 프로세서가 어떤 애노테이션을 처리 할 것 인지 정하는 메소드
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of(GetterImpl.class.getName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 해당 애노테이션이 붙어 있는 엘리먼트들을 가져 온다.
// Element : 클래스, 인터페이스, 메소드 등 애노테이션을 붙일 수 있는 target
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(GetterImpl.class);
for (Element element: elements) {
// 해당 Element 이름
Name simpleName = element.getSimpleName();
// 해당 Element가 Interface가 아닌 경우 빌드에서 에러나게 동작
if (element.getKind() != ElementKind.INTERFACE) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Getter annotation can not be used on " + simpleName);
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing " + simpleName);
}
/**
* 타입 앨리먼트를 가지고 있으면 ClassName으로 변환할 수 있다.
* 해당 어노테이션이 붙은 클래스와 같은 패키지에 소스파일을 생성하기 위해 클래스 관련 값을 얻는다
* */
TypeElement typeElement = (TypeElement) element;
ClassName className = ClassName.get(typeElement); //javapoet에서 제공하는 라이브러리, 클래스에 대한 정보 취득
// MethodSpec: Method 만드는 객체
MethodSpec getImpl = MethodSpec.methodBuilder("getImpl") //함수 이름
.addModifiers(Modifier.PUBLIC) // 접근 제한자 설정
.returns(String.class) // Method return Type 설정
.addStatement("return $S", "GetImpl!") // return 시 값 전달 설정
.build();
// TypeSpec : Type 만드는 객체
TypeSpec getterImpl = TypeSpec.classBuilder("GetterAnnotaionImpl") //사용할 클래스 이름
.addModifiers(Modifier.PUBLIC) // 접근 제한자 설정
.addMethod(getImpl)// @GetterImpl 가지고 있는 인터페이스를 구현
.addSuperinterface(className)
.build();
// Filer : 소스코드,클래스코드 및 리소스를 생성할 수 있는 인터페이스
// processingEnv : AbstractProcessor 상속 받으면 쓸 수 있는 전역 변수
Filer filer = processingEnv.getFiler();
try {
JavaFile.builder(className.packageName(), getterImpl)
.build()
.writeTo(filer);
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR : " + e);
}
}
return false;
}
}
//3. 인터페이스에 선언
@GetterImpl
public interface Getter {
String getImpl();
}
//4. 사용
Getter getter = new GetterAnnotaionImpl();
내 생각으로
주석처럼 프로그래밍 언어에 영향을 미치지 않으면서 유용한 정보제공이란 뜻은, 어노테이션은 컴파일 하기 전 까지 영향을 미치지 않는다는거 같다
[참조 및 출처]
https://velog.io/@eia51/Annotation-%EC%99%84%EC%A0%84-%EC%A0%95%EB%B3%B5%EA%B8%B0
https://developer-youn.tistory.com/122