lombok은 Annotation Processing과 Instrumentation을 사용한다. Instrumentation은 이클립스는 별도 컴파일러가 있기 때문에 이클립스 종속적인 bytecode 처리에 사용되고, AnnotationProcessor에서는 Annotation별로 코드를 생성하는데 사용된다.
@Getter String name;
public String getName() {
return this.name; }
AnnotationProcessor는 Java Compiler와 관련이 있다. 잠시 정리해 보자
javac는 크게 두가지 부분으로 나눈다. 하나는 ".java"을 ".class"로 바꾸는 컴파일러이고 다른 하나는 컴파일러를 지원하는 환경이다.
컴파일러 환경은 세가지 API 패키지를 제공하고
- Language Model API ( javax.lang.model package )
- Annotation Processing API ( javax.annotation.processing package )
- Compiler Tree API( com.sun.source package )
- Java Compiler API ( javax.tools package )
javac는 세가지 방식으로 호출 할 수 있다.
- com.sun.tools.javac.main.Main: 커멘드라인에서 직접 호출
- java Compiler API: javax.tools 패키지 이용
- Compiler Tree API: javax.tools.CompilationTask를 com.sun.source.util.JavacTask로 바꾸는 방법
- com.sun.source.util.Trees가 대표적이다. 일반적인 Tree API와 유사한 API를 제공한다.
Data Model은 com.sun.tools.javac.code.* 에서 Type별 Semantic 정보를 제공한다. - Symbol, Type, Annotations, Atrribute등으로 javax.lang.model.element.Element를 implement 하고 있다.
Annotation Processing은 Enter, MemberEnter 스캔 이후에 시작한다.
컴파일의 단계의 마지막으로 ".class" 생성에는 com.sun.tools.javac.jvm.* 이 이용된다.
흐름을 정리하면, 컴파일 하면 컴파일러는 AST를 생성시킨 후 "javac -processor"에 등록된 AnnotationProcessor를 호출한다. AnnotationProcessor에서는 컴파일러에서 제공된 Syntax Tree를 이용 하거나 직접 Visitor를 구현해서 트리를 탐색하며 필요한 데이터를 얻고 com.sun.source.tree.* 패키지를 이용해서 필요한 코드를 추가 할 수 있다.- Element 는 기본적으로 포함된 자식 element 들을 얻을 수 있지만, Symbol 과 같은 subtype에서는 코드의 위치 static여부 등의 상세한 내용들을 포함하고 있다.
- Enter, MemberEnter, Annotation Visitor에서 생성된다. Enter Visitor는 클래스 스캔을 MemberEnter Visitor는 클래스의 멤버 스캔을 그리고 Annotation Visitor는 Annotation을 스캔한다.
Annotation Processing은 Enter, MemberEnter 스캔 이후에 시작한다.
- Annotation Processing 중에는 컴파일에 필요한 부가적인 타입을 생성
- 분석 메소드 시그니쳐도 실행 할 수도 있다.
- 만약 Annotation Processing중 사용자가 파일을 새로 생성하면 컴파일은 재시작 된다. - 이 부분은 정확하지 않다. :)
컴파일의 단계의 마지막으로 ".class" 생성에는 com.sun.tools.javac.jvm.* 이 이용된다.
lombok 기본 원리인 AnnotationProcessor를 이용한 코드생성 예제
public @interface HelloWorld { }
import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; @SupportedAnnotationTypes({ "HelloWorld" }) @SupportedSourceVersion(SourceVersion.RELEASE_8) public class HelloProcessor extends AbstractProcessor { @Override public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { return false; } JavacProcessingEnvironment environment = (JavacProcessingEnvironment) processingEnv; Context context = environment.getContext(); TreeMaker maker = TreeMaker.instance(context); JavacElements elemUtils = (JavacElements) processingEnv.getElementUtils(); Class<HelloWorld> clazz = HelloWorld.class; Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(clazz); for (Element element : elements) { JCTree tree = elemUtils.getTree(element); JCTree.JCMethodDecl mDecl = (JCTree.JCMethodDecl) tree; injectHelloWorld(maker, elemUtils, mDecl); } return false; } private void injectHelloWorld( TreeMaker maker, JavacElements elemUtils, JCTree.JCMethodDecl mDecl) { maker.pos = mDecl.pos; List<JCExpression> nil = List.<JCTree.JCExpression> nil(); Name system = elemUtils.getName("System"); Name out = elemUtils.getName("out"); Name _println = elemUtils.getName("println"); JCLiteral helloworld = maker.Literal("Hello world"); mDecl.body = maker.Block(0,List.of( maker.Exec(maker.Apply(nil, maker.Select(maker.Select( maker.Ident( system), out), _println), List.<JCTree.JCExpression> of(helloworld))), mDecl.body) ); } }
public class Dummy { private String name; @HelloWorld public String getName() { return this.name; } public static void main(String... args) { new Dummy().getName(); } }
javac \ -cp target/apt-playground-1.0-SNAPSHOT.jar \ -processor HelloProcessor \ Dummy.java
전체 코드
https://github.com/rzwitserloot/lombok/blob/8037350c70a1172f88cd1f050336b326adabb64d/src/core/lombok/core/AnnotationProcessor.java
https://github.com/rzwitserloot/lombok/blob/a6170f5298daf42931877a2d9c98e6f2ad1985be/src/core/lombok/javac/apt/Processor.java
lombok Agent 참고 링크
https://github.com/rzwitserloot/lombok/blob/8037350c70a1172f88cd1f050336b326adabb64d/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java
댓글
댓글 쓰기