[Java] 어노테이션(Annotation)이란 (2)

hun·2023년 9월 8일

SpringBoot

목록 보기
5/8
post-thumbnail

이번엔 어노테이션 동작원리에 대해 알아보자.

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);
}

어노테이션 프로세스는 Processor를 구현한 추상 클래스 AbstractProcessor를 상속받아서 구현하면 된다.
  • process(Set<? extends TypeElement> annotations, RoundEnvironment env): 이것은 각각의 프로세서의 main() 메서드의 역할을 한다. 여기에 scanning, evaluating, 어노테이션 프로세싱, 자바 파일 생성을 위한 코드를 작성합니다. RoundEnvironment 를 파라미터를 가지고 여러분은 특정 어노테이션이 달린 것들을 찾을 수 있습니다.

Annotaion Processor 추가하여 만드는 예시 코드이다


//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

profile
짧더라도 확실한 기록

0개의 댓글