AOP - 03. 컴파일 타임 AOP

Seok-Hyun Lee·2022년 6월 1일
1

AOP

목록 보기
3/3
post-thumbnail

컴파일 타임 AOP

컴파일 타임 AOP 는 공통 기능 코드가 핵심 기능 코드에 삽입이 된 후에 컴파일이 되는 것을 의미해요

듣기만 해도 이걸 어떻게 하는거지? 라고 생각이 드실거에요
저도 이게 가능한가? 라는 의문을 가지고 있었는데
스프링을 사용하면 아주 익숙한 녀석이 있었습니다.
바로 Lombok 이에요

Lombok 은 Annotation Processor이고,
Annotation Processing 은 자바 컴파일러가 컴파일 단계에 소스 코드를 조작하거나 분석,처리 하는 것을 의미해요

그래서 저희가 Lombok 을 사용하면 @Getter, @Setter 등을 사용해서
소스 코드에 getter, setter 를 직접 명시해주지 않아도 되는 편리함을 누리고 있었습니다.

그럼 이걸 활용해서 직접 AOP 를 만들어 볼까요?

만들어보자

우선 아래와 같은 구조로 패키지를 만들었습니다.

인텔리제이를 기준으로 processor 라는 모듈을 만들고
이 안에 ann 과 proc 패키지를 만들었습니다

이때,TimeMeasure 어노테이션이 저희가 사용할 어노테이션이고,
TimeMeasureProcessor 가 Annotation Processing 을 정의한 클래스에요

이 다음 Project Structure 메뉴로 가서

메인 모듈에 dependency 를 추가해줍니다

자 이제 어노테이션과 프로세서를 만들어볼 차례인데요

우선, TimeMeasure 어노테이션은 Target 을 Method 로 한정지었습니다.

그리고 프로세서는 이렇게 만들었습니다.

@SupportedAnnotationTypes("ann.TimeMeasure")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class TimeMeasureProcessor extends AbstractProcessor {

    /** public for ServiceLoader */
    public TimeMeasureProcessor(){}

    // Annotation Processing 로직은 AbstractProcessor 를 상속받아 process 를 구현해야 함
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // annotation.ann.TimeMeasure 어노테이션이 적용된 모든 타입에 대한 루프
        for (Element e : roundEnv.getElementsAnnotatedWith(TimeMeasure.class)) {

            // 메소드에 사용된 것이 아니라면 컴파일 전 예외 발생
            if (e.getKind() != ElementKind.METHOD) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Not a method", e);
                continue;
            }

            // 어노테이션이 적용된 곳의 상위 타입
            TypeElement clazz = (TypeElement) e.getEnclosingElement();


            try {
                // 어노테이션 프로세싱이 적용된 파일의 이름
                JavaFileObject f = processingEnv.getFiler().createSourceFile(clazz.getQualifiedName() + "Proxy");
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + f.toUri());
                Writer writer = f.openWriter();
                PrintWriter printWriter = new PrintWriter(writer);

                try{
                    // 파일에 코드 작성
                    printWriter.println("package " + clazz.getEnclosingElement().getSimpleName() + ";");
                    printWriter.println("import java.time.LocalDateTime;");
                    printWriter.println(
                            "public class " + clazz.getSimpleName()+ "Proxy implements Singer { \n" +
                                    "\tprivate Singer singer;\n" +
                                    "\tpublic RnbSingerProxy(Singer singer){\n" +
                                    "\t\tthis.singer = singer;\n" +
                                    "\t}\n" +
                                    "\tpublic void sing() {\n" +
                                    "\t\tSystem.out.println(LocalDateTime.now());\n" +
                                    "\t\tsinger.sing();\n" +
                                    "\t\tSystem.out.println(LocalDateTime.now());\n" +
                                    "\t}\n"+
                                    "}");

                    printWriter.flush();
                } finally {
                    writer.close();
                }
            } catch (IOException x) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                        x.toString());
            }
        }
        return true;
    }
}

그리고 빌드를 하면 짜잔


generated 디렉터리에 새로운 파일이 만들어진 걸 볼 수 있어요


그리고 Project Structure 메뉴에서 generated 디렉터리를 excluded 에서 source로 바꾼 다음에 실행시켜주면

RnbSingerProxy 라는 클래스가 만들어져있는 걸 확인할 수 있어요

그리고 generated 에 있는 RnbSingerProxy 는 컴파일 단계에서 만들어지는 것이기 때문에
Main 클래스에서 이렇게 사용할 수 있어요

그리고 이걸 실행시키면 결과는

2022-06-01T17:34:18.338434400
Rnb Singer Singing
2022-06-01T17:34:18.339434700

요렇게 잘 나오는 걸 확인할 수 있습니다.

마무리

오늘은 Annotation Processor 라는 것을 활용해서 컴파일 단계에 필요한 기능을 삽입시켜주는 것을 만들어보았는데요

사실 엄밀히 말하면, 새로운 클래스를 만들어버린 것이기 때문에 삽입의 개념이라고는 볼 수 없다고 생각할 수 있어요

왜냐하면, Lombok 은 같은 클래스명을 유지한채 코드를 삽입시켜주니까요

하지만, Lombok 은 javac 를 다루는 것이기 때문에 더욱 저수준으로 내려가야 해서
오늘은 이렇게 컴파일 타임에 코드를 조작할 수 있는 방법이 있고
이를 활용해서 AOP 구현할 수 있음을 확인했다 정도로 생각해주시면 좋을 것 같아요 ㅎㅎ

그리고 다음 시간에는 3가지 구현 방법 중 마지막인 클래스 로딩 시점의 AOP 에 대한 글을 작성하도록 할게요!

참조
1. https://www.youtube.com/watch?v=HaCXOYptHqE&t=1304s
2. https://netbeans.apache.org/kb/docs/java/annotations-custom.html

profile
Arch-ITech

0개의 댓글