Lombok天天用,却不知道它的原理是什么()
文章图片
作者丨不学无数的程序员
来源丨掘金
链接:
https://juejin.im/post/5e54d38a6fb9a07cbf46b3ca
相信大家在项目中都使用过Lombok,因为能够简化我们许多的代码,但是该有的功能一点也不少。
那么lombok到底是个什么呢,lombok是一个可以通过简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 Java 代码的工具,简单来说,比如我们新建了一个类,然后在其中写了几个字段,然后通常情况下我们需要手动去建立getter和setter方法啊,构造函数啊之类的,lombok的作用就是为了省去我们手动创建这些代码的麻烦,它能够在我们编译源码的时候自动帮我们生成这些方法。
那么Lombok到底是如何做到这些的呢?其实底层就是用到了编译时注解的功能。
Lombok如何使用 Lombok是一个开源项目,代码是在lombok中,如果是gradle项目的话直接在项目中引用如下即可。
compile ("org.projectlombok:lombok:1.16.6")
功能
那么Lombok是做什么呢?其实很简单,一个最简单的例子就是能够通过添加注解自动生成一些方法,使我们代码更加简洁易懂。例如下面一个类。
1 @Data
2 public class TestLombok {
3private String name;
4private Integer age;
5
6public static void main(String[] args) {
7TestLombok testLombok = new TestLombok();
8testLombok.setAge(12);
9testLombok.setName("zs");
10}
11 }
我们使用Lombok提供的
Data
注解,在没有写get、set
方法的时候也能够使用其get、set
方法。我们看它编译过后的class
文件,可以看到它给我们自动生成了get、set
方法。 1 public class TestLombok {
2private String name;
3private Integer age;
4
5public static void main(String[] args) {
6TestLombok testLombok = new TestLombok();
7testLombok.setAge(12);
8testLombok.setName("zs");
9}
10
11public TestLombok() {
12}
13
14public String getName() {
15return this.name;
16}
17
18public Integer getAge() {
19return this.age;
20}
21
22public void setName(String name) {
23this.name = name;
24}
25
26public void setAge(Integer age) {
27this.age = age;
28}
29
30}
当然Lombok的功能不止如此,还有很多其他的注解帮助我们简便开发,网上有许多的关于Lombok的使用方法,这里就不再啰嗦了。正常情况下我们在项目中自定义注解,或者使用
Spring
框架中@Controller、@Service
等等这类注解都是运行时注解
,运行时注解大部分都是通过反射来实现的。而Lombok
是使用编译时注解实现的。那么编译时注解是什么呢?编译时注解
注解(也被成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。——————摘自《Thinking in Java》Java中的注解分为运行时注解和编译时注解,运行时注解就是我们经常使用的在程序运行时通过反射得到我们注解的信息,然后再做一些操作。而编译时注解是什么呢?就是在程序在编译期间通过注解处理器进行处理。
- 编译期:Java语言的编译期是一段不确定的操作过程,因为它可能是将
*.java
文件转化成*.class
文件的过程;也可能是指将字节码转变成机器码的过程;还可能是直接将*.java
编译成本地机器代码的过程
- 运行期:从JVM加载字节码文件到内存中,到最后使用完毕以后卸载的过程都属于运行期的范畴。
注解处理工具apt(Annotation Processing Tool),这是Sun为了帮助注解的处理过程而提供的工具,apt被设计为操作Java源文件,而不是编译后的类。它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心是AbstractProcessor类。
正常情况下使用APT工具只是能够生成一些文件(不仅仅是我们想象的class文件,还包括xml文件等等之类的),并不能修改原有的文件信息。
但是此时估计会有疑问,那么
Lombok
不就是在我们原有的文件中新增了一些信息吗?我在后面会有详细的解释,这里简单介绍一下,其实Lombok
是修改了Java中的**抽象语法树AST
**才做到了修改其原有类的信息。接下来我们演示一下如何用
APT
工具生成一个class文件,然后我们再说Lombok
是如何修改已存在的类中的属性的。定义注解
首先当然我们需要定义自己的注解了
1 @Retention(RetentionPolicy.SOURCE)
2 @Target(ElementType.TYPE)
3 public @interface GeneratePrint {
4
5String value();
6 }
Retention
注解上面有一个属性value,它是RetentionPolicy
类型的枚举类,RetentionPolicy
枚举类中有三个值。1 public enum RetentionPolicy {
2
3SOURCE,
4
5CLASS,
6
7RUNTIME
8 }
SOURCE
修饰的注解:修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中
CLASS
修饰的注解:表示注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候
RUNTIME
修饰的注解:表示注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时。所以它能够通过反射调用,所以正常运行时注解都是使用的这个参数
Target
注解上面也有个属性value,它是ElementType
类型的枚举。是用来修饰此注解作用在哪的。 1 public enum ElementType {
2TYPE,
3
4FIELD,
5
6METHOD,
7
8PARAMETER,
9
10CONSTRUCTOR,
11
12LOCAL_VARIABLE,
13
14ANNOTATION_TYPE,
15
16PACKAGE,
17
18TYPE_PARAMETER,
19
20TYPE_USE
21 }
定义注解处理器
我们要定义注解处理器的话,那么就需要继承
AbstractProcessor
类。继承完以后基本的框架类型如下 1 @SupportedSourceVersion(SourceVersion.RELEASE_8)
2 @SupportedAnnotationTypes("aboutjava.annotion.MyGetter")
3 public class MyGetterProcessor extends AbstractProcessor {
4@Override
5public synchronized void init(ProcessingEnvironment processingEnv) {
6super.init(processingEnv);
7}
8
9@Override
10public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
11return true;
12}
13 }
我们可以看到在子类中上面有两个注解,注解描述如下
@SupportedSourceVersion
:表示所支持的Java版本
@SupportedAnnotationTypes
:表示该处理器要处理的注解
- init方法:主要是获得编译时期的一些环境信息
- process方法:在编译时,编译器执行的方法。也就是我们写具体逻辑的地方
AbstractProcessor
类来实现在编译时生成类,所以我们在process
方法中书写我们生成类的代码。如下所示。
1 @Override
2 public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
3StringBuilder builder = new StringBuilder()
4.append("package aboutjava.annotion;
\n\n")
5.append("public class GeneratedClass {\n\n")
6.append("\tpublic String getMessage() {\n")
7.append("\t\treturn \"");
8
9for (Element element : roundEnv.getElementsAnnotatedWith(MyGetter.class)) {
10String objectType = element.getSimpleName().toString();
11
12builder.append(objectType).append(" says hello!\\n");
13}
14builder.append("\";
\n")
15.append("\t}\n")
16.append("}\n");
17try {
18JavaFileObject source = processingEnv.getFiler().createSourceFile("aboutjava.annotion.GeneratedClass");
19Writer writer = source.openWriter();
20writer.write(builder.toString());
21writer.flush();
22writer.close();
23} catch (IOException e) {
24
25
26}
27return true;
28 }
定义使用注解的类(测试类)
上面的两个类就是基本的工具类了,一个是定义了注解,一个是定义了注解处理器,接下来我们来定义一个测试类(TestAno.java)。我们在类上面加上我们自定的注解类。
1 @MyGetter
2 public class TestAno {
3
4public static void main(String[] args) {
5System.out.printf("1");
6}
7 }
这样我们在编译期就能生成文件了,接下来演示一下在编译时生成文件,此时不要着急直接进行javac编译,
MyGetter
类是注解类没错,而MyGetterProcessor
是注解类的处理器,那么我们在编译TestAno
Java文件的时候就会触发处理器。因此这两个类是无法一起编译的。先给大家看一下我的目录结构
aboutjava2
-- annotion3
-- MyGetter.java4
-- MyGetterProcessor.java5
-- TestAno.java
所以我们先将注解类和注解处理器类进行编译
javac aboutjava/annotion/MyGett*
接下来进行编译我们的测试类,此时在编译时需要加上
processor
参数,用来指定相关的注解处理类。javac -processor aboutjava.annotion.MyGetterProcessor aboutjava/annotion/TestAno.java
大家可以看到动态图中,自动生成了Java文件。
文章图片
MySQL用得好好的,为啥非要转ES?
2020-05-15
文章图片
使用码云(gitee)撸一个免费图床V2.0
2020-05-14
文章图片
半小时入门Shell编程
2020-05-12
文章图片
面试官问:如果MySQL引起CPU消耗过大,你会怎么优化?
2020-05-11
文章图片
DTO与DO的区别大家可能会有个疑问
2020-05-17
文章图片
关于Spring Cloud,你应该知道这些……
2020-05-19
文章图片
扫描二维码
获取更多精彩
趣学程序
文章图片
文章图片
【Lombok天天用,却不知道它的原理是什么()】在看点这里
文章图片
好文分享给更多人↓↓
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- JS中的各种宽高度定义及其应用
- 由浅入深理解AOP
- 【译】20个更有效地使用谷歌搜索的技巧
- D13|D13 张贇 Banner分析
- 涉毒患者(新诗)
- 参保人员因患病来不及到指定的医疗机构就医,能否报销医疗费用()
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询