您的位置:首页 > 科技 > 能源 > Lombok一文通

Lombok一文通

2024/12/23 9:40:36 来源:https://blog.csdn.net/littleschemer/article/details/139226524  浏览:    关键词:Lombok一文通

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 的工作原理主要包括以下几个步骤:

  1. 注解定义: Lombok 定义了一系列注解,这些注解可以被应用于 Java 类、方法、字段等元素上。

  2. 注解处理器: Lombok 包含了一个或多个注解处理器,这些处理器在编译时期被触发。当处理器检测到 Lombok 注解时,它会根据注解的配置生成相应的代码。

  3. 编译时代码生成: 使用 Lombok 注解的 Java 类在编译时,Lombok 的注解处理器会读取注解信息,并根据这些信息生成额外的 Java 代码。例如,当在字段上使用 @Getter@Setter 注解时,Lombok 会生成对应的 getter 和 setter 方法。

  4. AST 操作: Lombok 通过操作 Java 源文件的 AST 来插入生成的代码。AST 是源代码的树状结构表示,Lombok 通过修改 AST 来实现代码的自动添加。

  5. 源代码注入: 生成的代码会被注入到源文件中,通常作为单独的文件或在原有文件中以特殊的方式添加,以便编译器能够识别并编译这些新增的代码。

  6. IDE 集成: Lombok 还提供了对 IntelliJ IDEA 和 Eclipse 等 IDE 的支持。这意味着在使用这些 IDE 时,你可以获得完整的 Lombok 注解支持,包括代码补全、导航、重构等特性。

  7. 构建工具集成: Lombok 与 Maven 和 Gradle 等构建工具集成,确保在构建项目时能够正确处理 Lombok 注解。

  8. 无运行时依赖: 由于 Lombok 在编译时生成代码,因此它不会在运行时添加任何依赖或性能开销。

2.2.idea为什么需要引入lombok插件

Lombok 通过注解来自动生成代码,但这些注解发生在编译期。但我们使用ide进行开发的时候,在编写代码就已经需要知道注解的语义,因此通过插件,以便 IDE 能够理解 Lombok 注解并提供相应的代码补全、导航和错误检查功能。

  1. 注解处理器集成: Lombok 作为一个 Java 库,通过注解处理器在编译时生成额外的代码。IntelliJ IDEA 插件确保了这些注解处理器被正确集成到 IDE 中。

  2. 代码补全: 插件为 Lombok 提供的注解(如 @Getter, @Setter, @ToString 等)提供代码补全功能,使得开发更加高效。

  3. 导航和查找: 使用 Lombok 插件,IDEA 可以正确导航到 Lombok 生成的方法和构造函数,就像它们是手写的一样。

  4. 错误检查和代码分析: 插件允许 IDEA 对 Lombok 注解进行语义分析,提供错误和警告信息,帮助开发者避免潜在的问题。

  5. 预览生成的代码: 在某些情况下,插件允许开发者预览 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 {@Overridepublic 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

自动生成 toString() 方法,包括类的所有实例字段和final字段,不包括static字段。

@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日志接口
@Log4j2log4j2日志接口
@Slf4jSlf4j日志门面接口

下面以@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还有其他很多注解,但可能使用频率没那么广泛,感兴趣的读者可以自行查阅。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com