如何自己實現一個lombok?
lombok具有超級實用簡單的註解,減少了很多代碼的書寫,誰用誰知道。但是具有探索精神的程序員肯定會問他是怎麼實現的?
憑經驗我們知道,其是在編譯階段直接生成了代碼,與運行時是無關的,它的github地址:https://github.com/rzwitserloot/lombok
下面是一個很簡單的基本實現,主要涉及到以下知識點:
- Pluggable Annotation Processing
- AST(抽象語法樹)
- maven插件安裝
- javac編譯
項目結構
先來個簡單的測試:
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── jimo
│ │ │ ├── Data.java
│ │ │ ├── Main.java
│ │ │ └── MyProcessor.java
代碼
Data.java
package com.jimo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
}
Main.java
@Data
public class Main {
private int age;
private String name;
public static void main(String[] args) {
new Main();
System.out.println("yes");
}
}
MyProcessor.java
package com.jimo;
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 javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
@SupportedAnnotationTypes(value = {"com.jimo.Data"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("begin");
for (TypeElement annotation : annotations) {
if (annotation.getSimpleName().contentEquals("Data")) {
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : elementsAnnotatedWith) {
String pkg = element.getEnclosingElement().toString();
String className = element.getSimpleName().toString();
try {
rewriteClass(pkg, className);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("1:" + pkg);
System.out.println("2:" + className);
}
}
System.out.println("anno:" + annotation);
}
System.out.println(roundEnv);
return true;
}
private void rewriteClass(String pkg, String className) throws IOException {
final JavaFileObject sourceFile = processingEnv.getFiler().createClassFile(pkg + ".NewClass");
// JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(pkg + ".Test");
// JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(pkg + "." + className);
try (Writer writer = sourceFile.openWriter()) {
writer.write("package " + pkg + ";");
writer.write("class Main{");
writer.write("private int age;");
writer.write("}");
}
}
}
如何使用
javac編譯法
需要先編譯處理器,再編譯其他代碼
// 編譯處理器
src\main\java>javac com\jimo\MyProcessor.java
// 帶上處理器
javac -processor com.jimo.MyProcessor com\jimo\Main.java
然後會看到編譯時輸出:
com.jimo.Data
[errorRaised=false, rootElements=[com.jimo.Main], processingOver=false]
[errorRaised=false, rootElements=[], processingOver=true]
警告: 註釋處理不適用於隱式編譯的文件。
使用 -implicit 指定用於隱式編譯的策略。
1 個警告
maven插件編譯法
在插件中聲明處理器:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<annotationProcessors>
<annotationProcessor>
com.jimo.MyProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
同樣,我們也需要先編譯處理器:因爲默認,maven編譯是放在target/classes/com/jimo/xxx.class
,所以如下:
(編譯命令參考:javac -d ..\..\..\target\classes\ com\jimo\MyProcessor.java
)
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── jimo
│ │ │ ├── Data.java
│ │ │ ├── Main.java
│ │ │ └── MyProcessor.java
│ │ └── resources
│ └── test
│ └── java
└── target
├── classes
│ └── com
│ └── jimo
│ └── MyProcessor.class
然後使用maven compile
命令,或者在IDEA裏調用Lifecycle-->compile
按鈕:
結果:
[INFO] Compiling 3 source files to D:\workspace\test-demos\j-lombok-demo\target\classes
com.jimo.Data
[errorRaised=false, rootElements=[com.jimo.MyProcessor, com.jimo.Data, com.jimo.Main], processingOver=false]
[errorRaised=false, rootElements=[], processingOver=true]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
jar包引入
以上2種方式都是在一個倉庫裏使用,僅供開發測試,真正投入使用時,還是用jar包的方式好。
實踐lombok
上面的方法只能生成新的類和class文件,而有時需要修改源代碼和class,這就更復雜一些,需要懂得AST(抽象語法樹)和編譯原理。
安裝tools.jar 到本地倉庫:因爲需要用到裏面的AST代碼
mvn install:install-file -Dfile=JDK目錄\lib\tools.jar -DgroupId=com.sun -DartifactId=tools -Dversion=1.8 -Dpackaging=jar
寫處理器代碼:
package com.jimo;
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@SupportedAnnotationTypes(value = {"com.jimo.Data"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class CustomProcessor extends AbstractProcessor {
/**
* AST
*/
private JavacTrees trees;
/**
* 操作修改AST
*/
private TreeMaker treeMaker;
/**
* 符號封裝類,處理名稱
*/
private Names names;
/**
* 打印信息
*/
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = JavacTrees.instance(processingEnv);
final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
treeMaker = TreeMaker.instance(context);
names = Names.instance(context);
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("begin");
final Set<? extends Element> dataAnnotations = roundEnv.getElementsAnnotatedWith(Data.class);
dataAnnotations.stream().map(element -> trees.getTree(element)).forEach(
tree -> tree.accept(new TreeTranslator() {
@Override
public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
// print method name
System.out.println("-------------2");
messager.printMessage(Diagnostic.Kind.NOTE, jcMethodDecl.toString());
super.visitMethodDef(jcMethodDecl);
}
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
System.out.println("-------------1");
final Map<Name, JCTree.JCVariableDecl> treeMap =
jcClassDecl.defs.stream().filter(k -> k.getKind().equals(Tree.Kind.VARIABLE))
.map(tree -> (JCTree.JCVariableDecl) tree)
.collect(Collectors.toMap(JCTree.JCVariableDecl::getName, Function.identity()));
treeMap.forEach((k, var) -> {
messager.printMessage(Diagnostic.Kind.NOTE, "var:" + k);
System.out.println("-------------3");
try {
// add getter
jcClassDecl.defs = jcClassDecl.defs.prepend(getter(var));
// add setter
jcClassDecl.defs = jcClassDecl.defs.prepend(setter(var));
// jcClassDecl.defs.prepend(setter(var));
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
});
super.visitClassDef(jcClassDecl);
}
})
);
return true;
}
/**
* 自定義setter
*/
private JCTree setter(JCTree.JCVariableDecl var) throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
// 方法級別public
final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
final Name varName = var.getName();
Name methodName = methodName(varName, "set");
// 方法體
ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>();
jcStatements.append(treeMaker.Exec(treeMaker.Assign(
treeMaker.Select(treeMaker.Ident(names.fromString("this")), varName),
treeMaker.Ident(varName)
)));
final JCTree.JCBlock block = treeMaker.Block(0, jcStatements.toList());
// 返回值類型void
JCTree.JCExpression returnType =
treeMaker.Type((Type) (Class.forName("com.sun.tools.javac.code.Type$JCVoidType").newInstance()));
List<JCTree.JCTypeParameter> typeParameters = List.nil();
// 參數
final JCTree.JCVariableDecl paramVars = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER,
List.nil()), var.name, var.vartype, null);
final List<JCTree.JCVariableDecl> params = List.of(paramVars);
List<JCTree.JCExpression> throwClauses = List.nil();
// 重新構造一個方法, 最後一個參數是方法註解的默認值,這裏沒有
return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block,
null);
}
/**
* 構造駝峯命名
*/
private Name methodName(Name varName, String prefix) {
return names.fromString(prefix + varName.toString().substring(0, 1).toUpperCase()
+ varName.toString().substring(1));
}
/**
* 構造getter
*/
private JCTree getter(JCTree.JCVariableDecl var) {
// 方法級別
final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
// 方法名稱
final Name methodName = methodName(var.getName(), "get");
// 方法內容
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), var.getName())));
final JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
// 返回值類型
final JCTree.JCExpression returnType = var.vartype;
// 沒有參數類型
List<JCTree.JCTypeParameter> typeParameters = List.nil();
// 沒有參數變量
List<JCTree.JCVariableDecl> params = List.nil();
// 沒有異常
List<JCTree.JCExpression> throwClauses = List.nil();
// 構造getter
return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block,
null);
}
}
使用google的auto service進行配置處理器:
引入依賴:
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc4</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
<version>0.10</version>
<optional>true</optional>
</dependency>
注意到上面類中的@AutoService
註解就是來自這。
這樣編譯完後,會生成META-INF信息:
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── jimo
│ │ │ ├── CustomProcessor.java
│ │ │ ├── Data.java
│ │ └── resources
│ └── test
│ └── java
└── target
├── classes
│ ├── META-INF
│ │ └── services
│ │ └── javax.annotation.processing.Processor
│ └── com
│ └── jimo
│ ├── CustomProcessor$1.class
│ ├── CustomProcessor.class
│ ├── Data.class
javax.annotation.processing.Processor這個文本文件裏就一句話:
com.jimo.CustomProcessor
接着可以使用mvn install
安裝到本地,讓其他項目引入試試。
目標類:
@Data
public class User {
private int age;
private String name;
public int height;
}
編譯完後:
public class User {
private int age;
private String name;
public int height;
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setHeight(int height) {
this.height = height;
}
public User() {
}
}