1、Lombok简介
作为java的忠实粉丝,但也不得不承认,java是一门比较啰嗦的语言,很多代码的编写远不如其他静态语言方便,更别说跟脚本语言比较了。
因此,lombok应运而生。
Lombok是一种工具库,它提供了一组注解,用于简化Java代码的编写。它的主要目标是通过自动生成代码来减少样板代码的数量,从而提高开发人员的生产力。
使用Lombok,开发人员可以通过添加注解来自动生成getter和setter方法、构造函数、equals和hashCode方法,以及其他常见的样板代码。此外,Lombok还提供了一些其他的注解,可以自动生成日志记录、单例模式、Builder模式等。
Lombok的使用非常简单,只需在类或字段上添加注解即可。在编译时,Lombok会自动生成相应的代码,并将其写入源代码中。这意味着生成的代码会被编译器视为原始代码的一部分,从而允许开发人员在使用生成的代码时获得代码补全和其他IDE功能的支持。
总的来说,Lombok是一个能够减少Java代码冗余的有用工具,它可以帮助开发人员更高效地编写代码,提高开发速度和代码质量。
项目地址: lombok官网
2、Lombok原理
用了那么久lombok,就是觉得写代码方便很多了,但它底层的原理是什么呢?
Lombok 的核心原理是在编译时通过注解处理器(Annotation Processor)来操作抽象语法树(Abstract Syntax Tree, AST),从而扩展 Java 编译器的功能。
2.1.Lombok工作原理
Lombok 的工作原理主要包括以下几个步骤:
注解定义: Lombok 定义了一系列注解,这些注解可以被应用于 Java 类、方法、字段等元素上。
注解处理器: Lombok 包含了一个或多个注解处理器,这些处理器在编译时期被触发。当处理器检测到 Lombok 注解时,它会根据注解的配置生成相应的代码。
编译时代码生成: 使用 Lombok 注解的 Java 类在编译时,Lombok 的注解处理器会读取注解信息,并根据这些信息生成额外的 Java 代码。例如,当在字段上使用
@Getter
和@Setter
注解时,Lombok 会生成对应的 getter 和 setter 方法。AST 操作: Lombok 通过操作 Java 源文件的 AST 来插入生成的代码。AST 是源代码的树状结构表示,Lombok 通过修改 AST 来实现代码的自动添加。
源代码注入: 生成的代码会被注入到源文件中,通常作为单独的文件或在原有文件中以特殊的方式添加,以便编译器能够识别并编译这些新增的代码。
IDE 集成: Lombok 还提供了对 IntelliJ IDEA 和 Eclipse 等 IDE 的支持。这意味着在使用这些 IDE 时,你可以获得完整的 Lombok 注解支持,包括代码补全、导航、重构等特性。
构建工具集成: Lombok 与 Maven 和 Gradle 等构建工具集成,确保在构建项目时能够正确处理 Lombok 注解。
无运行时依赖: 由于 Lombok 在编译时生成代码,因此它不会在运行时添加任何依赖或性能开销。
2.2.idea为什么需要引入lombok插件
Lombok 通过注解来自动生成代码,但这些注解发生在编译期。但我们使用ide进行开发的时候,在编写代码就已经需要知道注解的语义,因此通过插件,以便 IDE 能够理解 Lombok 注解并提供相应的代码补全、导航和错误检查功能。
注解处理器集成: Lombok 作为一个 Java 库,通过注解处理器在编译时生成额外的代码。IntelliJ IDEA 插件确保了这些注解处理器被正确集成到 IDE 中。
代码补全: 插件为 Lombok 提供的注解(如
@Getter
,@Setter
,@ToString
等)提供代码补全功能,使得开发更加高效。导航和查找: 使用 Lombok 插件,IDEA 可以正确导航到 Lombok 生成的方法和构造函数,就像它们是手写的一样。
错误检查和代码分析: 插件允许 IDEA 对 Lombok 注解进行语义分析,提供错误和警告信息,帮助开发者避免潜在的问题。
预览生成的代码: 在某些情况下,插件允许开发者预览 Lombok 注解生成的代码,这有助于理解背后的行为。
3、插入式注解处理器
在 Java 中,插入式注解处理器(Pluggable Annotation Processor)允许开发者在编译时期自动处理特定的注解。下面是一个简单的示例,演示如何创建和使用自定义注解以及相应的注解处理器。
1.创建自定义注解
首先,定义一个注解,用来标记需要生成额外代码的类。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE) // 标记在类上
public @interface GenerateClass {
String value() default "DefaultClassName";
}
2.创建注解处理器
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.Processor;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import com.google.auto.service.AutoService;
import com.example.annotations.GenerateClass;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.annotation.processing.Filer;
import javax.tools.JavaFileObject;
import java.io.Writer;
import java.util.Set;
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.example.annotations.GenerateClass")
@SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_8)
public class GenerateClassProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(GenerateClass.class)) {
GenerateClass generateClass = element.getAnnotation(GenerateClass.class);
String className = generateClass.value();
try {
Filer filer = processingEnv.getFiler();
JavaFileObject builderFile = filer.createSourceFile(className);
try (Writer writer = builderFile.openWriter()) {
writer.write("public class " + className + " {\n");
writer.write(" public " + className + "() {\n");
writer.write(" System.out.println(\\\"New class generated!\\\");\n");
writer.write(" }\n");
writer.write("}\n");
}
} catch (Exception e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
}
return true;
}
}
3.注册注解处理器
如果你使用了 Google 的 AutoService
,则注解处理器会自动注册。否则,你需要在 META-INF/services
目录下创建一个名为 javax.annotation.processing.Processor
的文件,并填入你的处理器的全限定名。
import com.example.annotations.GenerateClass;
@GenerateClass(value = "MyGeneratedClass")
public class MyClass {
// 这个类会被注解处理器处理,并生成 MyGeneratedClass
}
4.编译项目
编译你的项目,注解处理器将自动运行,并根据 GenerateClass
注解生成新的类文件。
注意事项:
- 注解处理器是在编译时执行的,因此它们不会影响运行时性能。
- 确保你的注解处理器类被正确注册,以便编译器能够发现并使用它。
- 使用
@SupportedAnnotationTypes
和@SupportedSourceVersion
注解来声明你的处理器支持哪些注解和 Java 版本。 - 处理过程中发生的任何异常都需要被捕获并适当处理,通常通过
processingEnv.getMessager().printMessage()
打印错误信息。
4、Lombok示例
3.1.javabean注解
注解名称 | 作用 |
@Getter | 用于自动生成字段的get方法。作为类注解,则生成该类所有字段的getter方法(static字段不参与);作为字段注解,则生成该字段的getter方法。 |
@Setter | 用于自动生成字段的set方法。作为类注解,则生成该类所有字段的setter方法(final/static字段不参与);作为字段注解,则生成该字段的setter方法。 |
@ToString | 自动生成 |
@EqualsAndHashCode | 自动生成 equals() 和 hashCode() 方法 |
@NoArgsConstructor | 自动生成无参构造函数 |
@AllArgsConstructor | 自动生成包含所有字段的构造函数 |
@RequiredArgsConstructo | 自动生成包含所有非 final 和非静态字段的构造函数,并标记为 @NonNull 的字段 |
@Data | 组合注解,相当于 @Getter , @Setter , @RequiredArgsConstructor , @ToString , 和 @EqualsAndHashCode |
@Value | 类似 @Data ,但生成不可变对象(所有字段都是 final ) |
现在对@Data注解进行演示
假设一个普通的javabean定义如下:
import lombok.Data;
@Data
public class Person {
private int age;
private String name;
private static String key = "key";
private final String finalField = "FINALFIELD";
}
经过lombok插件的翻译之后,变成:
import java.util.Objects;
public class Person {
private int age;
private String name;
private static String key = "key";
private final String finalField = "FINALFIELD";
public Person() {
}
public int getAge() {
return this.age;
}
public String getName() {
return this.name;
}
public String getFinalField() {
Objects.requireNonNull(this);
return "FINALFIELD";
}
public void setAge(final int age) {
this.age = age;
}
public void setName(final String name) {
this.name = name;
}
public boolean equals(final 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 if (this.getAge() != other.getAge()) {
return false;
} else {
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
Object this$finalField = this.getFinalField();
Object other$finalField = other.getFinalField();
if (this$finalField == null) {
if (other$finalField != null) {
return false;
}
} else if (!this$finalField.equals(other$finalField)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = true;
int result = 1;
result = result * 59 + this.getAge();
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
Object $finalField = this.getFinalField();
result = result * 59 + ($finalField == null ? 43 : $finalField.hashCode());
return result;
}
public String toString() {
int var10000 = this.getAge();
return "Person(age=" + var10000 + ", name=" + this.getName() + ", finalField=" + this.getFinalField() + ")";
}
}
3.2.@Builder注解
@Builder也是一个非常常用的注解,为类提供构建器模式支持,允许链式调用。对于上面的Person定义,使用该注解之后,编译器会自动生成一个Builder类。代码大致如下:
public class Person$PersonBuilder {
private int age;
private String name;
Person$PersonBuilder() {
}
public Person$PersonBuilder age(final int age) {
this.age = age;
return this;
}
public Person$PersonBuilder name(final String name) {
this.name = name;
return this;
}
public Person build() {
return new Person(this.age, this.name);
}
public String toString() {
return "Person.PersonBuilder(age=" + this.age + ", name=" + this.name + ")";
}
}
如此,客户端便可已链式的方式进行创建实例,如下:
Person p = Person.builder().age(1).name("Tom").build();
3.3.日志类注解
lombok为各种常见的日志框架提供简易注解,有如下几个注解。
需要注意的是,
log注解只能用于类,接口,enum,或者record等。
注解只提供代码上的引用,项目仍然需要引入相关的依赖及配置才能使日志生效。
注解名称 | 作用 |
@Log |
jdk官方log接口 |
@Log4j |
log4j日志接口 |
@Log4j2 | log4j2日志接口 |
@Slf4j | Slf4j日志门面接口 |
下面以@slf4j注解进行演示说明
@Slf4j
public class TestLombok {
public static void main(String[] args) {
log.info("此处是日志");
}
}
翻译的代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestLombok {
private static final Logger log = LoggerFactory.getLogger(TestLombok.class);
public TestLombok() {
}
public static void main(String[] args) {
log.info("此处是日志");
}
}
3.4.@Cleanup注解
在jdk1.7之前,对于io资源的使用,一般是使用类似的代码,需要在io流使用结束后关闭资源。
import java.io.FileInputStream;
public class Test {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("input.txt");
} catch (Exception e) {
} finally {
if (fis != null) {
try {
fis.close();
} catch (Exception ignore) {
}
}
}
}
}
jdk1.7引入了try-with-resources语句块,可以自动处理单个或多个资源的关闭。类似下面的语法。
public static void main(String[] args) {
try ( FileInputStream fis = new FileInputStream("input.txt")){
// do sth
} catch (Exception ignored) {
}
}
@Cleanup做的也是类似的工作。个人觉得该注解比较鸡肋,因为jdk已经有官方的实现了。
源代码如下:
import lombok.Cleanup;
import java.io.FileInputStream;
public class Test {
public static void main(String[] args) throws Exception {
@Cleanup var file1 = new FileInputStream("input.txt");
@Cleanup var file2 = new FileInputStream("input.txt");
@Cleanup var file3 = new FileInputStream("input.txt");
}
}
翻译的代码如下:
import java.io.FileInputStream;
import java.util.Collections;
public class Test {
public Test() {
}
public static void main(String[] args) throws Exception {
FileInputStream file1 = new FileInputStream("input.txt");
try {
FileInputStream file2 = new FileInputStream("input.txt");
try {
FileInputStream file3 = new FileInputStream("input.txt");
if (Collections.singletonList(file3).get(0) != null) {
file3.close();
}
} finally {
if (Collections.singletonList(file2).get(0) != null) {
file2.close();
}
}
} finally {
if (Collections.singletonList(file1).get(0) != null) {
file1.close();
}
}
}
}
当然,lombok还有其他很多注解,但可能使用频率没那么广泛,感兴趣的读者可以自行查阅。