Lombok原理分析及简单实现
使用
maven引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
</dependency>
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
}
自动生成get,set方法,全参构造器,无参构造器
public class Client {
public static void main(String[] args) {
User user = new User();
user.setName("lisi");
System.out.println(user.getName());
}
}
原理
通过网上查资料,lombok的基本流程应该是
- 定义编译期的注解
- 利用JSR269 api(Pluggable Annotation Processing API )创建编译期的注解处理器
- 利用tools.jar的javac api处理AST(抽象语法树)
- 将功能注册进jar包
接下来自己实现一个类似功能的Getter注解。
项目依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.imooc</groupId>
<artifactId>mylombok</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
主要是tools.jar包的依赖和编译环境的配置。
创建Getter注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
}
注解在编译期起作用。
创建Getter注解处理器
@SupportedAnnotationTypes("com.imooc.mylombok.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends AbstractProcessor {
// 打印log
private Messager messager;
// 抽象语法树
private JavacTrees trees;
// 封装了创建AST节点的一些方法
private TreeMaker treeMaker;
// 提供了创建标识符的一些方法
private Names names;
// 初始化方法
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
// 真正处理注解的方法
@Override
public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取所有包含Getter注解的类
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
set.forEach(element -> {
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// 获取所有属性
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}
// 为每一个属性创建get方法
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
// 创建getter方法
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
}
// 获取方法名
private Name getNewMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
}
打包
在resources文件夹下创建META-INF文件夹,META-INF下创建services文件夹,services下创建文件名为javax.annotation.processing.Processor的文本文件,内容为com.imooc.mylombok.GetterProcessor。
添加这个文件是为了将自己添加为processor,方便其他项目调用,但自己编译时又不需要processor,就死循环了,自己在编译时不能添加services文件夹,但又需要打的包里有services文件夹,这时候需要配置maven插件。
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>META-INF/**/*</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>process-META</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/classes</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src/main/resources/</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行打包
mvn clean package
使用Getter注解
import com.imooc.mylombok.Getter;
@Getter
public class App {
private String value;
private String value2;
public App(String value) {
this.value = value;
}
public static void main(String[] args) {
App app = new App("it works");
System.out.println(app.getValue());
}
}
编译App
javac -cp mylombok-1.0-SNAPSHOT.jar App.java
运行APP
java App
结果为
it works
符合预期,说明Getter注解生效了,看一下App的反编译结果
public class App{
public String getValue2(){
return value2;
}
public String getValue(){
return value;
}
public App(String s){
value = s;
}
public static void main(String args[]){
App app = new App("it works");
System.out.println(app.getValue());
}
private String value;
private String value2;
}
确实生成了get方法。