├── .DS_Store ├── fakeLombok ├── .DS_Store ├── fakeLombok.iml ├── target │ ├── fakeLombok-1.0.jar │ ├── classes │ │ ├── META-INF │ │ │ └── services │ │ │ │ └── javax.annotation.processing.Processor │ │ └── me │ │ │ └── maru │ │ │ ├── anno │ │ │ ├── Get.class │ │ │ └── Set.class │ │ │ └── processor │ │ │ ├── GetProcessor$1.class │ │ │ ├── GetProcessor.class │ │ │ ├── SetProcessor$1.class │ │ │ ├── SetProcessor.class │ │ │ ├── GetProcessor$1$1.class │ │ │ └── SetProcessor$1$1.class │ ├── maven-archiver │ │ └── pom.properties │ └── maven-status │ │ └── maven-compiler-plugin │ │ └── compile │ │ └── default-compile │ │ ├── createdFiles.lst │ │ └── inputFiles.lst ├── src │ └── main │ │ └── java │ │ └── me │ │ └── maru │ │ ├── anno │ │ ├── Set.java │ │ └── Get.java │ │ └── processor │ │ ├── SetProcessor.java │ │ └── GetProcessor.java └── pom.xml ├── annoTest ├── annoTest.iml ├── target │ ├── maven-status │ │ └── maven-compiler-plugin │ │ │ ├── testCompile │ │ │ └── default-testCompile │ │ │ │ ├── createdFiles.lst │ │ │ │ └── inputFiles.lst │ │ │ └── compile │ │ │ └── default-compile │ │ │ ├── createdFiles.lst │ │ │ └── inputFiles.lst │ ├── annoTest-1.0-SNAPSHOT.jar │ ├── classes │ │ └── org │ │ │ └── example │ │ │ ├── App.class │ │ │ └── Car.class │ ├── test-classes │ │ └── org │ │ │ └── example │ │ │ └── AppTest.class │ └── maven-archiver │ │ └── pom.properties ├── src │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ ├── Car.java │ │ │ └── App.java │ └── test │ │ └── java │ │ └── org │ │ └── example │ │ └── FakeLombokTest.java └── pom.xml └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/.DS_Store -------------------------------------------------------------------------------- /fakeLombok/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/.DS_Store -------------------------------------------------------------------------------- /annoTest/annoTest.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fakeLombok/fakeLombok.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fakeLombok/target/fakeLombok-1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/fakeLombok-1.0.jar -------------------------------------------------------------------------------- /annoTest/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst: -------------------------------------------------------------------------------- 1 | org/example/AppTest.class 2 | -------------------------------------------------------------------------------- /annoTest/target/annoTest-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/annoTest/target/annoTest-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /annoTest/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst: -------------------------------------------------------------------------------- 1 | org/example/App.class 2 | org/example/Car.class 3 | -------------------------------------------------------------------------------- /annoTest/target/classes/org/example/App.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/annoTest/target/classes/org/example/App.class -------------------------------------------------------------------------------- /annoTest/target/classes/org/example/Car.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/annoTest/target/classes/org/example/Car.class -------------------------------------------------------------------------------- /fakeLombok/target/classes/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | me.maru.processor.GetProcessor 2 | me.maru.processor.SetProcessor 3 | -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/anno/Get.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/anno/Get.class -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/anno/Set.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/anno/Set.class -------------------------------------------------------------------------------- /annoTest/target/test-classes/org/example/AppTest.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/annoTest/target/test-classes/org/example/AppTest.class -------------------------------------------------------------------------------- /fakeLombok/target/maven-archiver/pom.properties: -------------------------------------------------------------------------------- 1 | #Generated by Maven 2 | #Fri Feb 05 21:52:39 KST 2021 3 | version=1.0 4 | groupId=me.maru 5 | artifactId=fakeLombok 6 | -------------------------------------------------------------------------------- /annoTest/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst: -------------------------------------------------------------------------------- 1 | /Users/lee-maru/Desktop/Study/src/test/java/org/example/AppTest.java 2 | -------------------------------------------------------------------------------- /annoTest/target/maven-archiver/pom.properties: -------------------------------------------------------------------------------- 1 | #Generated by Maven 2 | #Fri Feb 05 22:38:54 KST 2021 3 | version=1.0-SNAPSHOT 4 | groupId=org.example 5 | artifactId=annoTest 6 | -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/processor/GetProcessor$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/processor/GetProcessor$1.class -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/processor/GetProcessor.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/processor/GetProcessor.class -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/processor/SetProcessor$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/processor/SetProcessor$1.class -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/processor/SetProcessor.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/processor/SetProcessor.class -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/processor/GetProcessor$1$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/processor/GetProcessor$1$1.class -------------------------------------------------------------------------------- /fakeLombok/target/classes/me/maru/processor/SetProcessor$1$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lee-maru/fakeLombok/HEAD/fakeLombok/target/classes/me/maru/processor/SetProcessor$1$1.class -------------------------------------------------------------------------------- /annoTest/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst: -------------------------------------------------------------------------------- 1 | /Users/lee-maru/Desktop/Study/src/main/java/org/example/Car.java 2 | /Users/lee-maru/Desktop/Study/src/main/java/org/example/App.java 3 | -------------------------------------------------------------------------------- /annoTest/src/main/java/org/example/Car.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import me.maru.anno.Get; 4 | import me.maru.anno.Set; 5 | 6 | @Get @Set 7 | public class Car { 8 | private String name = "로드스터 2"; 9 | 10 | private String company = "테슬라"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /annoTest/src/main/java/org/example/App.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import me.maru.anno.Get; 4 | import me.maru.anno.Set; 5 | 6 | /** 7 | * Hello world! 8 | * 9 | */ 10 | @Get @Set 11 | public class App 12 | { 13 | 14 | public static void main(String[] args) { 15 | Car car1 = new Car(); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /fakeLombok/src/main/java/me/maru/anno/Set.java: -------------------------------------------------------------------------------- 1 | package me.maru.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.SOURCE) 10 | public @interface Set { 11 | } 12 | -------------------------------------------------------------------------------- /fakeLombok/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst: -------------------------------------------------------------------------------- 1 | me/maru/processor/GetProcessor$1.class 2 | me/maru/anno/Get.class 3 | me/maru/anno/Set.class 4 | me/maru/processor/GetProcessor$1$1.class 5 | me/maru/processor/GetProcessor.class 6 | me/maru/processor/SetProcessor.class 7 | META-INF/services/javax.annotation.processing.Processor 8 | me/maru/processor/SetProcessor$1.class 9 | me/maru/processor/SetProcessor$1$1.class 10 | -------------------------------------------------------------------------------- /fakeLombok/src/main/java/me/maru/anno/Get.java: -------------------------------------------------------------------------------- 1 | package me.maru.anno; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Fake lombok : 7 | * 1. 클래스 선언부 위로 선언할 시, 클래스 안에있는 8 | * 필드를 모두 인식하여, 바이트코드에서 getter 메서드를 자동생성 9 | * 2. @Get 메서드는 추 후 문제가 될 수 있으며, openApi 를 사용하여 개발한 것이 10 | * 아니라는점을 알고 사용하시길 바랍니다. 11 | * 12 | * @author maru 13 | * @since 1.0 14 | */ 15 | @Documented 16 | @Target(ElementType.TYPE) 17 | @Retention(RetentionPolicy.SOURCE) 18 | public @interface Get { 19 | } 20 | -------------------------------------------------------------------------------- /fakeLombok/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst: -------------------------------------------------------------------------------- 1 | /Users/lee-maru/Downloads/projects-master/annotation-processor/fakeLombok/src/main/java/me/maru/anno/Get.java 2 | /Users/lee-maru/Downloads/projects-master/annotation-processor/fakeLombok/src/main/java/me/maru/processor/GetProcessor.java 3 | /Users/lee-maru/Downloads/projects-master/annotation-processor/fakeLombok/src/main/java/me/maru/anno/Set.java 4 | /Users/lee-maru/Downloads/projects-master/annotation-processor/fakeLombok/src/main/java/me/maru/processor/SetProcessor.java 5 | -------------------------------------------------------------------------------- /annoTest/src/test/java/org/example/FakeLombokTest.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | class FakeLombokTest { 9 | // given 10 | Car car1 = new Car(); 11 | 12 | @Test 13 | @DisplayName("getter 메소드 테스트") 14 | void testGetter(){ 15 | 16 | //when 17 | String name = car1.getName(); 18 | String company = car1.getCompany(); 19 | 20 | //then 21 | assertThat(name).isEqualTo("로드스터 2"); 22 | assertThat(company).isEqualTo("테슬라"); 23 | } 24 | 25 | @Test 26 | @DisplayName("setter 메소드 테스트") 27 | void testSetter(){ 28 | 29 | //when 30 | car1.setName("소나타"); 31 | car1.setCompany("현대"); 32 | String name = car1.getName(); 33 | String company = car1.getCompany(); 34 | 35 | //then 36 | assertThat(name).isEqualTo("소나타"); 37 | assertThat(company).isEqualTo("현대"); 38 | } 39 | 40 | 41 | } -------------------------------------------------------------------------------- /fakeLombok/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | me.maru 8 | fakeLombok 9 | 1.0 10 | fakeLombok 11 | 12 | 13 | UTF-8 14 | 1.8 15 | 1.8 16 | 1.0-rc4 17 | 18 | 19 | 20 | 21 | 22 | com.google.auto.service 23 | auto-service 24 | ${auto-service.version} 25 | true 26 | 27 | 28 | 29 | com.github.olivergondza 30 | maven-jdk-tools-wrapper 31 | 0.1 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /annoTest/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | org.example 8 | annoTest 9 | 1.0-SNAPSHOT 10 | 11 | annoTest 12 | 13 | 14 | UTF-8 15 | 1.8 16 | 1.8 17 | 18 | 19 | 20 | 21 | 22 | me.maru 23 | fakeLombok 24 | 1.0 25 | 26 | 27 | org.junit.jupiter 28 | junit-jupiter 29 | RELEASE 30 | test 31 | 32 | 33 | org.assertj 34 | assertj-core-java8 35 | 1.0.0m1 36 | test 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /fakeLombok/src/main/java/me/maru/processor/SetProcessor.java: -------------------------------------------------------------------------------- 1 | package me.maru.processor; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.sun.source.tree.ClassTree; 5 | import com.sun.source.tree.CompilationUnitTree; 6 | import com.sun.source.util.TreePath; 7 | import com.sun.source.util.TreePathScanner; 8 | import com.sun.source.util.Trees; 9 | import com.sun.tools.javac.code.TypeTag; 10 | import com.sun.tools.javac.processing.JavacProcessingEnvironment; 11 | import com.sun.tools.javac.tree.JCTree; 12 | import com.sun.tools.javac.tree.TreeMaker; 13 | import com.sun.tools.javac.tree.TreeTranslator; 14 | import com.sun.tools.javac.util.Context; 15 | import com.sun.tools.javac.util.List; 16 | import com.sun.tools.javac.util.Names; 17 | 18 | import javax.annotation.processing.*; 19 | import javax.lang.model.SourceVersion; 20 | import javax.lang.model.element.Element; 21 | import javax.lang.model.element.TypeElement; 22 | import javax.tools.JavaFileObject; 23 | import java.util.Set; 24 | 25 | @SupportedAnnotationTypes("me.maru.anno.Set") 26 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 27 | @AutoService(Processor.class) 28 | public class SetProcessor extends AbstractProcessor { 29 | private ProcessingEnvironment processingEnvironment; 30 | private Trees trees; 31 | private TreeMaker treeMaker; 32 | private Names names; 33 | private Context context; 34 | 35 | @Override 36 | public synchronized void init(ProcessingEnvironment processingEnv) { 37 | JavacProcessingEnvironment javacProcessingEnvironment = (JavacProcessingEnvironment) processingEnv; 38 | super.init(processingEnv); 39 | this.processingEnvironment = processingEnv; 40 | this.trees = Trees.instance(processingEnv); 41 | this.context = javacProcessingEnvironment.getContext(); 42 | this.treeMaker = TreeMaker.instance(context); 43 | this.names = Names.instance(context); 44 | } 45 | 46 | @Override 47 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 48 | TreePathScanner scanner = new TreePathScanner(){ 49 | @Override 50 | public Trees visitClass(ClassTree classTree, CompilationUnitTree unitTree){ 51 | JCTree.JCCompilationUnit compilationUnit = (JCTree.JCCompilationUnit) unitTree; 52 | if (compilationUnit.sourcefile.getKind() == JavaFileObject.Kind.SOURCE){ 53 | compilationUnit.accept(new TreeTranslator() { 54 | @Override 55 | public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { 56 | super.visitClassDef(jcClassDecl); 57 | 58 | List members = jcClassDecl.getMembers(); 59 | 60 | for(JCTree member : members){ 61 | if (member instanceof JCTree.JCVariableDecl){ 62 | List setters = createSetter((JCTree.JCVariableDecl) member); 63 | for(JCTree.JCMethodDecl setter : setters){ 64 | System.out.println("setter " + setter); 65 | jcClassDecl.defs = jcClassDecl.defs.prepend(setter); 66 | } 67 | } 68 | } 69 | } 70 | }); 71 | } 72 | return trees; 73 | } 74 | }; 75 | 76 | for (final Element element : roundEnv.getElementsAnnotatedWith(me.maru.anno.Set.class)) { 77 | final TreePath path = trees.getPath(element); 78 | scanner.scan(path, path.getCompilationUnit()); 79 | } 80 | 81 | 82 | 83 | return true; 84 | } 85 | 86 | public List createSetter(JCTree.JCVariableDecl var){ 87 | JCTree.JCVariableDecl param = treeMaker.Param(names.fromString("_"+var.getName().toString()), var.vartype.type, null); 88 | String str = var.name.toString(); 89 | String upperVar = str.substring(0,1).toUpperCase()+str.substring(1,var.name.length()); 90 | return List.of( 91 | treeMaker.MethodDef( 92 | treeMaker.Modifiers(1), 93 | names.fromString("set".concat(upperVar)), 94 | treeMaker.TypeIdent(TypeTag.VOID), 95 | List.nil(), 96 | List.of(param), 97 | List.nil(), 98 | treeMaker.Block(0, List.of(treeMaker.Exec(treeMaker.Assign( 99 | treeMaker.Ident(var), 100 | treeMaker.Ident(param.name))))), 101 | null)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /fakeLombok/src/main/java/me/maru/processor/GetProcessor.java: -------------------------------------------------------------------------------- 1 | package me.maru.processor; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.sun.source.tree.ClassTree; 5 | import com.sun.source.tree.CompilationUnitTree; 6 | import com.sun.source.util.TreePath; 7 | import com.sun.source.util.TreePathScanner; 8 | import com.sun.source.util.Trees; 9 | import com.sun.tools.javac.processing.JavacProcessingEnvironment; 10 | import com.sun.tools.javac.tree.JCTree; 11 | import com.sun.tools.javac.tree.TreeMaker; 12 | import com.sun.tools.javac.tree.TreeTranslator; 13 | import com.sun.tools.javac.util.Context; 14 | import com.sun.tools.javac.util.List; 15 | import com.sun.tools.javac.util.Names; 16 | import me.maru.anno.Get; 17 | 18 | import javax.annotation.processing.*; 19 | import javax.lang.model.SourceVersion; 20 | import javax.lang.model.element.Element; 21 | import javax.lang.model.element.ElementKind; 22 | import javax.lang.model.element.TypeElement; 23 | import javax.tools.Diagnostic; 24 | import javax.tools.JavaFileObject; 25 | import java.util.Set; 26 | 27 | /** 28 | * SupportedAnnotationTypes 어떤 어노테이션을 위한 프로세서 인가? 29 | * SupportedSourceVersion jdk 지원 정보 30 | * AutoService(Processor.class) MAINFEST 자동생성 31 | */ 32 | @SupportedAnnotationTypes("me.maru.anno.Get") 33 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 34 | @AutoService(Processor.class) 35 | public class GetProcessor extends AbstractProcessor { 36 | private ProcessingEnvironment processingEnvironment; 37 | private Trees trees; 38 | private TreeMaker treeMaker; 39 | private Names names; 40 | private Context context; 41 | 42 | /** 43 | * 1. names 추후 메소드를 생성에서, parm or method 이름 생성을 위함. 44 | * 2. Treemaker Abstract Syntax Tree 를 make 하는 메소드 제공 45 | * 예) method 정의, parameter 값 정의 etc.. 46 | */ 47 | 48 | @Override 49 | public synchronized void init(ProcessingEnvironment processingEnv) { 50 | JavacProcessingEnvironment javacProcessingEnvironment = (JavacProcessingEnvironment) processingEnv; 51 | super.init(processingEnv); 52 | this.processingEnvironment = processingEnv; 53 | this.trees = Trees.instance(processingEnv); 54 | this.context = javacProcessingEnvironment.getContext(); 55 | this.treeMaker = TreeMaker.instance(context); 56 | this.names = Names.instance(context); 57 | } 58 | 59 | /** 60 | * process 의 리턴값으로 어놈테이션을 처리하고 난 뒤, 다른 어노테이션이 지원되지 않도록 조정 61 | * @return true (이 필드, 클래스는 끝남) or false (이, 필드 클래스는 끝나지 않음) 62 | */ 63 | @Override 64 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 65 | System.out.println("process 메서드 실행"); 66 | // TreePathScanner 모든 하위 트리노드를 방문하고, 상위 노드에 대한 경로를 유지하는 tree visitor 67 | TreePathScanner scanner = new TreePathScanner(){ 68 | /** 69 | * CompillationUnitTree 는 소스파일에서 패키지 선언에서 부터 abstract syntax tree 를 정의함 70 | * ClassTree -> 클래스 , 인터페이스, enum 어노테이션을 트리노드로 선언 71 | * class 정의 위에 어노테이션 작성시 내부적으로 메소드 실행 72 | * CompilationUnitTree AST(Abstract Syntax Tree 의 최상단) 73 | */ 74 | @Override 75 | public Trees visitClass(ClassTree classTree, CompilationUnitTree unitTree){ 76 | JCTree.JCCompilationUnit compilationUnit = (JCTree.JCCompilationUnit) unitTree; 77 | // .java 파일인지 확인후 accept 를 통해 treeTransLator, 작성 메소드 생성 78 | if (compilationUnit.sourcefile.getKind() == JavaFileObject.Kind.SOURCE){ 79 | compilationUnit.accept(new TreeTranslator() { 80 | @Override 81 | public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { 82 | super.visitClassDef(jcClassDecl); 83 | // Class 내부에 정의된 모든 member 를 싹다 가져옴. 84 | List members = jcClassDecl.getMembers(); 85 | // Syntax tree 에서 모든 member 변수 get 86 | for(JCTree member : members){ 87 | if (member instanceof JCTree.JCVariableDecl){ 88 | // member 변수에 대한 getter 메서드 생성 89 | List getters = createGetter((JCTree.JCVariableDecl) member); 90 | for(JCTree.JCMethodDecl getter : getters){ 91 | jcClassDecl.defs = jcClassDecl.defs.prepend(getter); 92 | } 93 | } 94 | } 95 | } 96 | }); 97 | } 98 | return trees; 99 | } 100 | }; 101 | /** 102 | * RoundEnvironment 103 | * getElementsAnnotatedWith() -> @Get 의 어노테이션이 붙여져 있는 모든 element 를 불러 일으킨다. 104 | */ 105 | for (final Element element : roundEnv.getElementsAnnotatedWith(Get.class)) { 106 | // 현재 어노테이션은 Type 이고 여기서 Class 뿐만 아니라, interface 와 enum 에도 작성이 가능하므로 class만 지정할 수 있도록 107 | if(element.getKind() != ElementKind.CLASS){ 108 | processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Get annotation cant be used on" + element.getSimpleName()); 109 | }else{ 110 | processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "@Get annotation Processing " + element.getSimpleName()); 111 | final TreePath path = trees.getPath(element); 112 | scanner.scan(path, path.getCompilationUnit()); 113 | } 114 | } 115 | 116 | return true; 117 | } 118 | 119 | public List createGetter(JCTree.JCVariableDecl var){ 120 | // 필드 이름 변수에 앞문자 대문자로 변경 해주기 121 | String str = var.name.toString(); 122 | String upperVar = str.substring(0,1).toUpperCase()+str.substring(1,var.name.length()); 123 | 124 | return List.of( 125 | /** 126 | * treeMaker.Modifiers -> syntax tree node 에 접근하여 수정및 삽입하는 역할 127 | * @Parm : treeMaker.Modifiers flag 1-> public , 2-> private, 0-> default 128 | * @Parm : methodName & Type, return 정의 129 | */ 130 | treeMaker.MethodDef( 131 | treeMaker.Modifiers(1), // public 132 | names.fromString("get".concat(upperVar)), // 메서드 명 133 | (JCTree.JCExpression) var.getType(), // return type 134 | List.nil(), 135 | List.nil(), 136 | List.nil(), 137 | // 식생성 this.a = a; 138 | treeMaker.Block(1, List.of(treeMaker.Return((treeMaker.Ident(var.getName()))))), 139 | null)); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Lombok @Getter, @Setter 직접 만들어 보자 2 | 3 | ![스크린샷 2021-02-04 22 47 55](https://user-images.githubusercontent.com/70433341/107016523-61bcd580-67e1-11eb-85e5-871adb97bfd6.png) ![스크린샷 2021-02-04 22 48 18](https://user-images.githubusercontent.com/70433341/107016574-6da89780-67e1-11eb-9db6-73cc30d6070e.png) 4 | 5 | ### Q : 왜 롬복을 만들어 보려고 하는가? 6 | 7 | ### A : 알고 쓰고 싶어서 8 | 9 | 우리는 롬복을 정말 많이 사용하고는 한다. 하지만, 이 롬복이 실제로 어떻게 작동하는지에 대해서 아는사람은 많이 적을 거라고 예상한다. 나 자신도, 롬복은 그저 **마법** 같은 존재였을 뿐이었고, 보통 **이렇게만 사용하는구나,** 라고만 생각했다. 우연히 **백기선님의 '[The JAVA, 코드를 조작하는 다양한 방법](https://www.inflearn.com/course/the-java-code-manipulation)' 의 소개 영상에서 롬복의 밑단에서 어떤 일이 발생하는지에 대해, 우리가 사용하는 코드 밑단에서 무슨일이 발생하는지에 대해서 강의가 오픈되었다고 한다.** 10 | 11 | 이 강의에 끌리기 시작했고, 무작정 강의를 듣기 시작했다. 재밌게 잘들었지만, 롬복에 대한 호기심은 사라지지 않았고, 직접 롬복을 비슷하게 만들어본 사람이 있을까? 찾아봤지만, 한국에서는 찾기 어려웠고, **중국의 어느 개발자분이 롬복은 어떤 기술을 사용했습니다. 정도의 수준으로 정리한글** 들이 있었다. 이 글을 정독하면서, 12 | 13 | [Lombok经常用,但是你知道它的原理是什么吗 ?](https://juejin.cn/post/6844904072789622792) 14 | 15 | [Lombok经常用,但是你知道它的原理是什么吗?(二)](https://juejin.cn/post/6844904082084233223) 16 | 17 | **많은 사람들이 이 글을 보면서 롬복에 대해서 간단하게 알 수 있으면 했고, 이런 오픈소스를 직접 개발을 해보고싶은 욕망이 있었기에 롬복을 만들어보자라는 생각이 들어 개발하게 되었다.** 18 | 19 | 참고 : 롬복은 Java OpenAPI를 사용하지 않는다. 물론 그렇기 때문에, 공식문서 자료가 많이 없고, 코드에 대한 설명이 부족할 수 있다. 참고 자료는 올려 놓고, 전체코드의 주석을 최대한 꼼꼼하게 작성했다. 또한 이 글에서는 롬복의 @Getter, @Setter 어노테이션을 직접 만들 것이다. 20 | 21 | --- 22 | 23 | ### 개발환경 24 | 25 | * Tool : intelliJ 2020.2.3 (2020.2.4 버전에서 일부 기능이 오류가 발생하는 이슈가 있어, 일부러 다운그레이드 시켜야만 했다.) 26 | * Language : Java jdk 1.8.0_261 (oracle) 27 | * ProjectName : fakeLombok 28 | * 구현내용 : @Get(getter), @Set(setter) 29 | 30 | ### mvn porm.xml 31 | 32 | ```xml 33 | 34 | 35 | 37 | 4.0.0 38 | 39 | me.maru 40 | fakeLombok 41 | 1.0 42 | fakeLombok 43 | 44 | 45 | UTF-8 46 | 1.8 47 | 1.8 48 | 1.0-rc4 49 | 50 | 51 | 52 | 53 | 54 | com.google.auto.service 55 | auto-service 56 | ${auto-service.version} 57 | true 58 | 59 | 60 | 61 | com.github.olivergondza 62 | maven-jdk-tools-wrapper 63 | 0.1 64 | 65 | 66 | 67 | 68 | 69 | ``` 70 | 71 | 72 | 73 | 74 | ### @Get 어노테이션 정의하기 75 | 76 | ​ @Getter 어노테이션을 정의만 하는건 간단하다. 하지만, 지금 Getter 와 같은 어노테이션을 만들 때, 사용자에게 이 어노테이션의 기능 스펙, 또는 사용법을 최소한으로 작성하긴 해야한다. 77 | 78 | ```java 79 | package me.maru.anno; 80 | 81 | import java.lang.annotation.*; 82 | 83 | /** 84 | * Fake lombok : 85 | * 1. 클래스 선언부 위로 선언할 시, 클래스 안에있는 86 | * 필드를 모두 인식하여, 바이트코드에서 getter 메서드를 자동생성 87 | * 2. @Get 메서드는 추 후 문제가 될 수 있으며, openApi 를 사용하여 개발한 것이 88 | * 아니라는점을 알고 사용하시길 바랍니다. 89 | * 90 | * @author maru 91 | */ 92 | @Documented 93 | @Target(ElementType.TYPE) 94 | @Retention(RetentionPolicy.SOURCE) 95 | public @interface Get { 96 | } 97 | ``` 98 | 99 | 100 | 101 | * @Documented : 이 어노테이션의 스펙을 자바 문서화 시키는 걸 얘기를 한다. 위에 같이 주석을 문서화 시킬 수 있다. 즉, javadoc으로 api 문서를 만들 때, 어노테이션에 대한 설명을 포함할 수 있게 지정해 주는 것이다. 102 | 103 | * @Target : 어노테이션을 적용할 수 있는 위치를 의미한다. 104 | 105 | Type: Class, Interface(annotation), enum 106 | 107 | Field, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, etc.. 108 | 109 | * @Retention : 어노테이션을 어디까지 가지고 사용할 것이냐 이다. 지금 만들어야할 @Get 은 컴파일하고 난 뒤에 필요가 없기 때문에 SOURCE 상에서만 유지하기로 하자. 110 | --- 111 | ### 어노테이션 프로세스 112 | 113 | ​ 어노테이션 프로세서라고 함은, 내가 만든 어노테이션에 구체적인 동작 행위를 하기위해서 자바에서 제공하는 api 이다. @Get 어노테이션을 사용하기 위해서 @GetProcessor를 만들어보도록 하자. 우리는 **AbstractProcessor 를 extends 받아서 개발하자.** 114 | 115 | #### GetProcessor 클래스 생성 116 | 117 | ```java 118 | /** 119 | * SupportedAnnotationTypes 어떤 어노테이션을 위한 프로세서 인가? 120 | * SupportedSourceVersion jdk 지원 정보 121 | * AutoService(Processor.class) MAINFEST 자동생성 122 | */ 123 | @SupportedAnnotationTypes("me.maru.anno.Get") 124 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 125 | @AutoService(Processor.class) 126 | public class GetProcessor extends AbstractProcessor { 127 | 128 | } 129 | ``` 130 | 131 | * SupportedAnnotationTypes 어떤 어노테이션을 위한 프로세서 인가? 132 | * SupportedSourceVersion jdk 지원 정보 133 | * AutoService(Processor.class) MAINFEST 자동생성 134 | 135 | #### init 메소드 136 | 137 | init 메서드를 오버라이딩 하여, 컴파일시 정보를 얻어야 한다. 예를들어 대표적으로 syntax tree 에 대한 정보를 얻어오는걸 근간으로 합니다. 138 | 139 | ```java 140 | /** 141 | * 1. names 추후 메소드를 생성에서, parm or method 이름 생성을 위함. 142 | * 2. Treemaker Abstract Syntax Tree 를 make 하는 메소드 제공 143 | * 예) method 정의, parameter 값 정의 etc.. 144 | */ 145 | @Override 146 | public synchronized void init(ProcessingEnvironment processingEnv) { 147 | JavacProcessingEnvironment javacProcessingEnvironment = (JavacProcessingEnvironment) processingEnv; 148 | super.init(processingEnv); 149 | this.processingEnvironment = processingEnv; 150 | this.trees = Trees.instance(processingEnv); 151 | this.context = javacProcessingEnvironment.getContext(); 152 | this.treeMaker = TreeMaker.instance(context); 153 | this.names = Names.instance(context); 154 | } 155 | ``` 156 | 157 | * Names 추후 메소드를 생성하여 parm or method 이름 생성을 위함 158 | * Treemaker : Abstact Syntax Tree 를 생성하는데 사용하게 된다. JCTree는 AST를 만들어내는 최상위 클래스 이다. 하지만 JCTree를 이용하여 new 를 사용하여 직접 생성할 수 없기에 Context를 이용해 AST 를 인식하고 [Treemaker](http://www.docjar.com/docs/api/com/sun/tools/javac/tree/TreeMaker.html) 라는 객체를 사용해야 한다는 것이다. 수정함 예) method 정의, method 의 parm 값 정의 159 | * Trees : 어노테이션 프로스세의 process의 RoundEnvironment 가 코드의 element를 순회 하면서 받는 element의 정보들을 trees 에 넣기위에 선언 160 | 161 | 162 | 163 | #### Process 메소드 164 | 165 | 이제 직접 AST를 수정해야 한다. annotation processor 의 비지니스 로직은 process 메서드를 통해서 이루어 진다. return 값은 boolean 으로. java compiler 가 return 값이 true 이면, 이 어노테이션을 처리했고, 다른 annotation processor 가 처리하지 않아도 된다. 라고 해준다. 166 | 167 | ```java 168 | /** 169 | * process 의 리턴값으로 어놈테이션을 처리하고 난 뒤, 다른 어노테이션이 지원되지 않도록 조정 170 | * @return true (이 필드, 클래스는 끝남) or false (이, 필드 클래스는 끝나지 않음) 171 | */ 172 | @Override 173 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 174 | System.out.println("process 메서드 실행"); 175 | // TreePathScanner 모든 하위 트리노드를 방문하고, 상위 노드에 대한 경로를 유지하는 tree visitor 176 | TreePathScanner scanner = new TreePathScanner(){ 177 | /** 178 | * CompillationUnitTree 는 소스파일에서 패키지 선언에서 부터 abstract syntax tree 를 정의함 179 | * ClassTree -> 클래스 , 인터페이스, enum 어노테이션을 트리노드로 선언 180 | * class 정의 위에 어노테이션 작성시 내부적으로 메소드 실행 181 | * CompilationUnitTree AST(Abstract Syntax Tree 의 최상단) 182 | */ 183 | @Override 184 | public Trees visitClass(ClassTree classTree, CompilationUnitTree unitTree){ 185 | JCTree.JCCompilationUnit compilationUnit = (JCTree.JCCompilationUnit) unitTree; 186 | // .java 파일인지 확인후 accept 를 통해 treeTransLator, 작성 메소드 생성 187 | if (compilationUnit.sourcefile.getKind() == JavaFileObject.Kind.SOURCE){ 188 | compilationUnit.accept(new TreeTranslator() { 189 | @Override 190 | public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { 191 | super.visitClassDef(jcClassDecl); 192 | // Class 내부에 정의된 모든 member 를 싹다 가져옴. 193 | List members = jcClassDecl.getMembers(); 194 | // Syntax tree 에서 모든 member 변수 얻음. 195 | for(JCTree member : members){ 196 | if (member instanceof JCTree.JCVariableDecl){ 197 | // member 변수에 대한 getter 메서드 생성. 198 | List getters = createGetter((JCTree.JCVariableDecl) member); 199 | for(JCTree.JCMethodDecl getter : getters){ 200 | jcClassDecl.defs = jcClassDecl.defs.prepend(getter); 201 | } 202 | } 203 | } 204 | } 205 | }); 206 | } 207 | return trees; 208 | } 209 | }; 210 | 211 | /** 212 | * RoundEnvironment 213 | * getElementsAnnotatedWith() -> @Get 의 어노테이션이 붙여져 있는 모든 element 를 불러 일으킨다. 214 | */ 215 | for (final Element element : roundEnv.getElementsAnnotatedWith(Get.class)) { 216 | // 현재 어노테이션은 Type 이고 여기서 Class 뿐만 아니라, interface 와 enum 에도 작성이 가능하므로 class만 지정할 수 있도록 217 | if(element.getKind() != ElementKind.CLASS){ 218 | processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Get annotation cant be used on" + element.getSimpleName()); 219 | }else{ 220 | processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "@Get annotation Processing " + element.getSimpleName()); 221 | final TreePath path = trees.getPath(element); 222 | scanner.scan(path, path.getCompilationUnit()); 223 | } 224 | } 225 | 226 | return true; 227 | } 228 | 229 | public List createGetter(JCTree.JCVariableDecl var){ 230 | // 필드 이름 변수에 앞문자 대문자로 변경 해주기 231 | String str = var.name.toString(); 232 | String upperVar = str.substring(0,1).toUpperCase()+str.substring(1,var.name.length()); 233 | 234 | return List.of( 235 | /** 236 | * treeMaker.Modifiers -> syntax tree node 에 접근하여 수정및 삽입하는 역할 237 | * @Parm : treeMaker.Modifiers flag 1-> public , 2-> private, 0-> default 238 | * @Parm : methodName & Type, return 정의 239 | */ 240 | treeMaker.MethodDef( 241 | treeMaker.Modifiers(1), // public 242 | names.fromString("get".concat(upperVar)), // 메서드 명 243 | (JCTree.JCExpression) var.getType(), // return type 244 | List.nil(), 245 | List.nil(), 246 | List.nil(), 247 | // 식생성 this.a = a; 248 | treeMaker.Block(1, List.of(treeMaker.Return((treeMaker.Ident(var.getName()))))), 249 | null)); 250 | } 251 | ``` 252 | 253 | 254 | 255 | 1. @Get 어노테이션이 붙여져 있는 클래스를 찾은 후에 Syntax tree를 가져오도록 한다. 256 | 2. tree 내부에서 element의 member 변수를 가지는 노드를 찾고 직접 메소드를 생성하고, 직접 method를 만들어 sytax tree 의 node를 만들어 준다. 257 | 258 | 259 | 260 | #### 간단하게 로직을 표현 (그림) 261 | ![image](https://user-images.githubusercontent.com/70433341/107035116-01d32880-67fb-11eb-9222-6d0a678127d0.png) 262 | 263 | ![image](https://user-images.githubusercontent.com/70433341/107034954-cc2e3f80-67fa-11eb-8eaa-7f917d140d25.png) 264 | 265 | ![image](https://user-images.githubusercontent.com/70433341/107034701-6b066c00-67fa-11eb-8300-00e62558c752.png) 266 | 267 | 268 | 269 | 이제 직접 만들어 본, 어노테이션을 사용해보도록 하자. 270 | 271 | Annotation 작성한 프로젝트에서 mvn clean install 을 해주도록 하자. autoService 의 도움으로 jar 패키징도 문제 없을거고 내가만든 프로젝트를 메이븐프로젝트에 의존성 주입해보도록 해보자. 272 | 273 | ```xml 274 | 275 | 276 | 277 | me.maru 278 | fakeLombok 279 | 1.0 280 | 281 | 282 | org.junit.jupiter 283 | junit-jupiter 284 | RELEASE 285 | test 286 | 287 | 288 | 289 | ``` 290 | 291 | 292 | 293 | #### 어노테이션 직접 사용하기 294 | 295 | ```java 296 | package org.example; 297 | 298 | import me.maru.anno.Get; 299 | import me.maru.anno.Set; 300 | 301 | @Get @Set 302 | public class Car { 303 | private String name = "로드스터 2"; 304 | private String company = "테슬라"; 305 | 306 | } 307 | 308 | //decomile .class file 309 | 310 | // 311 | // Source code recreated from a .class file by IntelliJ IDEA 312 | // (powered by FernFlower decompiler) 313 | // 314 | 315 | package org.example; 316 | 317 | public class Car { 318 | private String name = "로드스터 2"; 319 | private String company = "테슬라"; 320 | 321 | public void setCompany(String _company) { 322 | this.company = _company; 323 | } 324 | 325 | public void setName(String _name) { 326 | this.name = _name; 327 | } 328 | 329 | public String getCompany() { 330 | return this.company; 331 | } 332 | 333 | public String getName() { 334 | return this.name; 335 | } 336 | 337 | public Car() { 338 | } 339 | } 340 | 341 | ``` 342 | 343 | 344 | 345 | #### 테스트 코드 346 | 347 | ```java 348 | package org.example; 349 | 350 | import org.junit.jupiter.api.DisplayName; 351 | import org.junit.jupiter.api.Test; 352 | 353 | import static org.assertj.core.api.Assertions.assertThat; 354 | 355 | class FakeLombokTest { 356 | // given 357 | Car car1 = new Car(); 358 | 359 | @Test 360 | @DisplayName("getter 메소드 테스트") 361 | void testGetter(){ 362 | 363 | //when 364 | String name = car1.getName(); 365 | String company = car1.getCompany(); 366 | 367 | //then 368 | assertThat(name).isEqualTo("로드스터 2"); 369 | assertThat(company).isEqualTo("테슬라"); 370 | } 371 | 372 | @Test 373 | @DisplayName("setter 메소드 테스트") 374 | void testSetter(){ 375 | 376 | //when 377 | car1.setName("소나타"); 378 | car1.setCompany("현대"); 379 | String name = car1.getName(); 380 | String company = car1.getCompany(); 381 | 382 | //then 383 | assertThat(name).isEqualTo("소나타"); 384 | assertThat(company).isEqualTo("현대"); 385 | } 386 | } 387 | ``` 388 | 389 | 390 | 391 | Setter 까지 만들어 놓은 모든 파일은 깃을 통해 확인 가능하십니다. 392 | 393 | --------------------------------------------------------------------------------