作者:九年义务教育漏网之鱼 \
链接:https://juejin.cn/post/684490…
置信大家在我的项目中都应用过 Lombok,因为可能简化咱们许多的代码,然而该有的性能一点也不少。那么 lombok 到底是个什么呢,lombok 是一个能够通过简略的注解的模式来帮忙咱们简化打消一些必须有但显得很臃肿的 Java 代码的工具。
简略来说,比方咱们新建了一个类,而后在其中写了几个字段,而后通常状况下咱们须要手动去建设 getter 和 setter 办法啊,构造函数啊之类的,lombok 的作用就是为了省去咱们手动创立这些代码的麻烦,它可能在咱们编译源码的时候主动帮咱们生成这些办法。
那么 Lombok 到底是如何做到这些的呢?其实底层就是用到了编译时注解的性能。
Lombok 如何应用
Lombok 是一个开源我的项目,代码是在 lombok 中,如果是 gradle 我的项目的话间接在我的项目中援用如下即可。
compile ("org.projectlombok:lombok:1.16.6")
性能
那么 Lombok 是做什么呢?其实很简略,一个最简略的例子就是可能通过增加注解主动生成一些办法,使咱们代码更加简洁易懂。例如上面一个类。
@Data
public class TestLombok {
private String name;
private Integer age;
public static void main(String[] args) {TestLombok testLombok = new TestLombok();
testLombok.setAge(12);
testLombok.setName("zs");
}
}
咱们应用 Lombok 提供的 Data
注解,在没有写 get、set
办法的时候也可能应用其 get、set
办法。咱们看它编译过后的 class
文件,能够看到它给咱们主动生成了 get、set
办法。
public class TestLombok {
private String name;
private Integer age;
public static void main(String[] args) {TestLombok testLombok = new TestLombok();
testLombok.setAge(12);
testLombok.setName("zs");
}
public TestLombok() {}
public String getName() {return this.name;}
public Integer getAge() {return this.age;}
public void setName(String name) {this.name = name;}
public void setAge(Integer age) {this.age = age;}
}
当然 Lombok 的性能不止如此,还有很多其余的注解帮忙咱们简便开发,网上有许多的对于 Lombok 的应用办法,这里就不再啰嗦了。失常状况下咱们在我的项目中自定义注解,或者应用 Spring
框架中 @Controller、@Service
等等这类注解都是 运行时注解
,运行时注解大部分都是通过反射来实现的。而Lombok
是应用编译时注解实现的。那么编译时注解是什么呢?
编译时注解
注解(也被成为元数据)为咱们在代码中增加信息提供了一种形式化的办法,使咱们能够在稍后某个时刻十分不便地应用这些数据。——————摘自《Thinking in Java》
Java 中的注解分为 运行时注解 和编译时注解,运行时注解就是咱们常常应用的在程序运行时通过反射失去咱们注解的信息,而后再做一些操作。而编译时注解是什么呢?就是在程序在编译期间通过注解处理器进行解决。
- 编译期:Java 语言的编译期是一段不确定的操作过程,因为它可能是将
*.java
文件转化成*.class
文件的过程;也可能是指将字节码转变成机器码的过程;还可能是间接将*.java
编译成本地机器代码的过程 - 运行期:从 JVM 加载字节码文件到内存中,到最初应用结束当前卸载的过程都属于运行期的领域。
注解解决工具 apt
注解解决工具 apt(Annotation Processing Tool),这是 Sun 为了帮忙注解的处理过程而提供的工具,apt 被设计为操作 Java 源文件,而不是编译后的类。
它是 javac 的一个工具,中文意思为编译时注解处理器。APT 能够用来在编译时扫描和解决注解。通过 APT 能够获取到注解和被注解对象的相干信息,在拿到这些信息后咱们能够依据需要来主动的生成一些代码,省去了手动编写。留神,获取注解及生成代码都是在代码 编译 时候实现的,相比反射在运行时解决注解大大提高了程序性能。APT 的外围是 AbstractProcessor 类。
失常状况下应用 APT 工具只是可能生成一些文件 ( 不仅仅是咱们设想的 class 文件,还包含 xml 文件等等之类的),并不能批改原有的文件信息。
然而此时预计会有疑难,那么 Lombok
不就是在咱们原有的文件中新增了一些信息吗?我在前面会有具体的解释,这里简略介绍一下,其实 Lombok
是批改了 Java 中的 形象语法树 AST
才做到了批改其原有类的信息。
接下来咱们演示一下如何用 APT
工具生成一个 class 文件,而后咱们再说 Lombok
是如何批改已存在的类中的属性的。
定义注解
首先当然咱们须要定义本人的注解了
@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于润饰类
public @interface GeneratePrint {String value();
}
Retention
注解下面有一个属性 value,它是 RetentionPolicy
类型的枚举类,RetentionPolicy
枚举类中有三个值。
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
SOURCE
润饰的注解:润饰的注解, 示意注解的信息会被编译器摈弃,不会留在 class 文件中,注解的信息只会留在源文件中CLASS
润饰的注解:示意注解的信息被保留在 class 文件 (字节码文件) 中当程序编译时,但不会被虚拟机读取在运行的时候RUNTIME
润饰的注解:示意注解的信息被保留在 class 文件 (字节码文件) 中当程序编译时,会被虚拟机保留在运行时。所以它可能通过反射调用,所以失常运行时注解都是应用的这个参数
Target
注解下面也有个属性 value,它是 ElementType
类型的枚举。是用来润饰此注解作用在哪的。
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
定义注解处理器
举荐一个 Spring Boot 基础教程及实战示例:
https://github.com/javastacks…
咱们要定义注解处理器的话,那么就须要继承 AbstractProcessor
类。继承完当前根本的框架类型如下
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("aboutjava.annotion.MyGetter")
public class MyGetterProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {return true;}
}
咱们能够看到在子类中下面有两个注解,注解形容如下
@SupportedSourceVersion
:示意所反对的 Java 版本@SupportedAnnotationTypes
:示意该处理器要解决的注解
继承了父类的两个办法,办法形容如下
- init 办法:次要是取得编译期间的一些环境信息
- process 办法:在编译时,编译器执行的办法。也就是咱们写具体逻辑的中央
咱们是演示一下如何通过继承 AbstractProcessor
类来实现在编译时生成类,所以咱们在 process
办法中书写咱们生成类的代码。如下所示。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {StringBuilder builder = new StringBuilder()
.append("package aboutjava.annotion;\n\n")
.append("public class GeneratedClass {\n\n") // open class
.append("\tpublic String getMessage() {\n") // open method
.append("\t\treturn \"");
// for each javax.lang.model.element.Element annotated with the CustomAnnotation
for (Element element : roundEnv.getElementsAnnotatedWith(MyGetter.class)) {String objectType = element.getSimpleName().toString();
// this is appending to the return statement
builder.append(objectType).append("says hello!\\n");
}
builder.append("\";\n") // end return
.append("\t}\n") // close method
.append("}\n"); // close class
try { // write the file
JavaFileObject source = processingEnv.getFiler().createSourceFile("aboutjava.annotion.GeneratedClass");
Writer writer = source.openWriter();
writer.write(builder.toString());
writer.flush();
writer.close();} catch (IOException e) {// Note: calling e.printStackTrace() will print IO errors
// that occur from the file already existing after its first run, this is normal
}
return true;
}
定义应用注解的类(测试类)
下面的两个类就是根本的工具类了,一个是定义了注解,一个是定义了注解处理器,接下来咱们来定义一个测试类(TestAno.java)。咱们在类下面加上咱们自定的注解类。
@MyGetter
public class TestAno {public static void main(String[] args) {System.out.printf("1");
}
}
这样咱们在编译期就能生成文件了,接下来演示一下在编译时生成文件,此时不要焦急间接进行 javac 编译,MyGetter
类是注解类没错,而 MyGetterProcessor
是注解类的处理器,那么咱们在编译TestAno
Java 文件的时候就会触发处理器。因而这两个类是无奈一起编译的。
先给大家看一下我的目录构造
aboutjava
-- annotion
-- MyGetter.java
-- MyGetterProcessor.java
-- TestAno.java
所以咱们先将注解类和注解处理器类进行编译
javac aboutjava/annotion/MyGett*
接下来进行编译咱们的测试类,此时在编译时须要加上 processor
参数,用来指定相干的注解解决类。
javac -processor aboutjava.annotion.MyGetterProcessor aboutjava/annotion/TestAno.java
大家能够看到动态图中,主动生成了 Java 文件。
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)
2. 劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!