LHJ's Blog

Lombok原理解析与实现

✨ 简介

Lombok是一个特别实用的工具,通过添加注解的方式(比如@Data、@NoArgContructor等)可以节省很多的代码和开发精力(可以想象一下以前的生成get、set方法以后,如果要改字段的痛苦🤣)

🎭 原理分析

Lombok的主要特点是通过注解的方式提高开发效率,所以其原理就在解析注解上。可以给一个Perosn类添加注解@Data,然后编译后通过IDEA的反编译功能查看一下编译后代码的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
//Person.java
package com.longhujing.jlombok;

import lombok.Data;

@Data
public class Person {

private String name;

private int age;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//Person.class -> Person.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.longhujing.jlombok;

public class Person {
private String name;
private int age;

public Person() {
}

public String getName() {
return this.name;
}

public int getAge() {
return this.age;
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name == null) {
return this.getAge() == other.getAge();
}
} else if (this$name.equals(other$name)) {
return this.getAge() == other.getAge();
}

return false;
}
}
}

protected boolean canEqual(Object other) {
return other instanceof Person;
}

public int hashCode() {
int PRIME = true;
int result = 1;
Object $name = this.getName();
int result = result * 59 + ($name == null ? 43 : $name.hashCode());
result = result * 59 + this.getAge();
return result;
}

public String toString() {
return "Person(name=" + this.getName() + ", age=" + this.getAge() + ")";
}
}

可以看到最终生成的class文件里面有get、set等方法,说明了Lombok是在编译的时候通过解析注解,就已经生成好了文件,所以在运行的时候加载生成了get、set方法的class文件就可以调用get、set方法了。

Java在编译时解析注解的方式有两种:Annotation Processing Tool(起源于JDK5,JDK8已经废除)和Pluggable Annotation Processing API,目前Lombok的主要实现原理就是Pluggable Annotation Processing API。

Pluggable Annotation Processing API涉及到AST(抽象语法树)的知识,Java代码在执行之前是需要经过编译的,而这个编译过程从上到下分别是:词法分析、语法分析、语义分析、中间代码生成、代码优化、最终代码生成等步骤。而在语法分析之后会生成一棵语法树,通过语法树来分析代码语义的问题以及生成第一版本的代码。那也就意味着:可以通过在编译的过程中修改语法树来达到修改最终生成代码的目的,这也是Lombok的实现原理。

🎄 手动实现Lombok的主要功能

限于篇幅,其它Processor的具体实现可以在我的Github上找到

实现的主要注解是:@Getter、@Setter、@AllArgConstructor、@NoArgConstructor

具体实现代码如下:

🔥 添加必要的依赖

1
2
3
4
5
6
7
8
<!-- 1.8已经不包含这个包了,需要手动引入 -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

💖 annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.longhujing.jlombok.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 生成get方法
* @author longhujing
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface Getter {
}

💖 processor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.longhujing.jlombok.processor;

import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;

/**
* Processor的一层抽象,主要做一些初始化工作
* @author longhujing
*/
public abstract class BaseProcessor extends AbstractProcessor {

/**
* 抽象语法树
*/
protected JavacTrees trees;

/**
* 打印信息
*/
protected Messager messager;

/**
* 用于修改语法树
*/
protected TreeMaker treeMaker;

/**
* 用于抽象语法树中的节点命名
*/
protected Names names;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
treeMaker = TreeMaker.instance(context);
names = Names.instance(context);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.longhujing.jlombok.processor;

import com.longhujing.jlombok.annotation.Getter;
import com.longhujing.jlombok.util.ProcessorUtils;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeTranslator;

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.TypeElement;
import java.util.Set;

/**
* Getter注解处理器,在编译时解析Getter注解,生成get方法
* @author longhujing
*/
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.longhujing.jlombok.annotation.Getter")
public class GetterProcessor extends BaseProcessor {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 找到有Getter注解的类
roundEnv.getElementsAnnotatedWith(Getter.class).stream()
// 获取语法树
.map(element -> trees.getTree(element))
// 对语法树进行操作
.forEach(jcTree -> jcTree.accept(new TreeTranslator(){
/**
* 由于是在类上加Getter注解,所以应该操作类语法树
* @param jcClassDecl 类定义
*/
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
jcClassDecl.defs.stream()
// 排除掉不合法的变量定义
.filter(ProcessorUtils::isVariableValid)
// 转化为JCVariable
.map(tree -> (JCTree.JCVariableDecl) tree)
// 排除掉get方法已经存在的字段
.filter(jcVariableDecl -> !ProcessorUtils.isGetMethodExist(jcClassDecl, jcVariableDecl))
// 生成get方法
.forEach(jcVariableDecl -> jcClassDecl.defs = jcClassDecl.defs
.append(ProcessorUtils.createGetMethod(treeMaker, names, jcVariableDecl)));
super.visitClassDef(jcClassDecl);
}
}));
return false;
}

}

✌ 把processor注册到服务里面

resources下创建文件夹,最后的结构是resources/META-INF/services,在resources/META-INF/services下添加一个文件javax.annotation.processing.Processor,文件内容是

1
2
3
4
com.longhujing.jlombok.processor.GetterProcessor
com.longhujing.jlombok.processor.SetterProcessor
com.longhujing.jlombok.processor.NoArgConstructorProcessor
com.longhujing.jlombok.processor.AllArgConstructorProcessor

💦 添加插件,打包资源

pom.xml文件中添加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<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>

👣 使用自定义的注解

首先将上一步做好的JLombok安装到本地仓库

1
mvn install

新建一个Maven工程,添加自定义的jar包

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>com.longhujing</groupId>
<artifactId>JLombok</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

编写一个简单的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.longhujing.entity;

import com.longhujing.jlombok.annotation.AllArgConstructor;
import com.longhujing.jlombok.annotation.Getter;
import com.longhujing.jlombok.annotation.Setter;

@Getter
@Setter
@AllArgConstructor
public class Person {

private String name;

private int age;

}

执行mvn compile命令,查看生成的class文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.longhujing.entity;

public class Person {
private String name;
private int age;

public Person() {
}

public String getName() {
return this.name;
}

public int getAge() {
return this.age;
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

可以看到确实生成了get、set方法。当然你也可以直接在程序中调用getName或者getAge等方法,编译器会冒红,是正常情况。执行的时候不会出现错误。


 评论