【工具类库】Lombok简介、使用、工作原理、优缺点–最全一篇Lombok使用讲解及原理真香啊

没有人挡得住,你疯狂的努力进取。你可以不够强大,但你不能没有梦想。如果你没有梦想,你只能为别人的梦想打工筑路。

导读:本篇文章讲解 【工具类库】Lombok简介、使用、工作原理、优缺点–最全一篇Lombok使用讲解及原理真香啊,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

简介

官方介绍

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.Never write another
getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much
more.

大概的意思:Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。通过添加注解的方式,不需要为类编写getter或eques方法,同时可以自动化日志变量。官网链接
简而言之:Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。

Lombok 是一款好用顺手的工具,就像 Google Guava 一样。可用来帮助开发人员消除 Java 的冗长代码,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。

通过在开发环境中实现 Lombok,开发人员可以节省构建诸如hashCode()和equals()这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。

Lombok使用

使用Lombok需要的开发环境Java+Maven+IntelliJ IDEA或者Eclipse(安装Lombok Plugin)

IntelliJ 安装 Lombok

  • 通过 IntelliJ 的插件中心安装
    在这里插入图片描述

  • Install Plugin
    在这里插入图片描述

添加maven依赖(导入 lombok 包)

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.4</version>
	<scope>provided</scope>
</dependency>

解决编译时出错问题

编译时出错,可能是没有enable注解处理器。Annotation Processors > Enable annotation processing。设置完成之后程序正常运行。
在这里插入图片描述

Lombok 注解大全说明

  • val:用在局部变量前面,相当于将变量声明为 final
  • @NonNull:给方法参数增加这个注解,会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出 NPE(NullPointerException)
  • @Cleanup:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成 try-finally 这样的代码来关闭流
  • @Getter/@Setter:用在属性上,再也不用自己手写 setter 和 getter 方法了,还可以指定访问范围
  • @ToString:用在类上,可以自动覆写 toString 方法,当然还可以加其他参数,例如@ToString(exclude=”id”)排除 id 属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的 toString 方法,包含所有属性
  • @EqualsAndHashCode:用在类上,自动生成 equals 方法和 hashCode 方法
  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有@NonNull 属性作为参数的构造函数,如果指定 staticName = “of”参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多
  • @Data:注解在类上,相当于同时使用了@ToString、@EqualsAndHashCod- e、@Getter、@Setter 和@RequiredArgsConstrutor 这些注解,对于 POJO 类十分有用
  • @Value:用在类上,是@Data 的不可变形式,相当于为属性添加 final 声明,只提供 getter 方法,而不提供 setter 方法
  • @Builder:用在类、构造器、方法上,为你提供复杂的 builder APIs,让你可以像如下方式一样调用 Person.builder().name(“Adam Savage”).city(“San Francisco”).job(“Mythbusters”).job(“Unchained Reaction”).build();更多说明参考 Builder
  • @SneakyThrows:自动抛受检异常,而无需显式在方法上使用 throws 语句
  • @Synchronized:用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性 或LOCK,而 java 中的 synchronized 关键字锁对象是 this,锁在 this 或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁 this 或者类对象,这可能会导致竞争条件或者其它线程错误
  • @Getter(lazy=true):可以替代经典的 Double Check Lock 样板代码
  • @Log:根据不同的注解生成不同类型的 log 对象,但是实例名称都是 log,有六种可选实现类
    • @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
    • @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());
    • @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);
    • @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
    • @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
    • @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

Lombok 代码示例

val 示例

public static void main(String[] args) {
  val sets = new HashSet<String>();
  val lists = new ArrayList<String>();
  val maps = new HashMap<String, String>();
  //=>相当于如下
  final Set<String> sets2 = new HashSet<>();
  final List<String> lists2 = new ArrayList<>();
  final Map<String, String> maps2 = new HashMap<>();
}

@NonNull 示例

public void notNullExample(@NonNull String string) {
    string.length();
}
//=>相当于
public void notNullExample(String string) {
  if (string != null) {
      string.length();
  } else {
      throw new NullPointerException("null");
  }
}

@Cleanup 示例

public static void main(String[] args) {
  try {
      @Cleanup InputStream inputStream = new FileInputStream(args[0]);
  } catch (FileNotFoundException e) {
      e.printStackTrace();
  }
  //=>相当于
  InputStream inputStream = null;
  try {
      inputStream = new FileInputStream(args[0]);
  } catch (FileNotFoundException e) {
      e.printStackTrace();
  } finally {
      if (inputStream != null) {
          try {
              inputStream.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  }
}

@Getter/@Setter 示例

@Setter(AccessLevel.PUBLIC)
@Getter(AccessLevel.PROTECTED)
private int id;
private String shap;

@ToString 示例

@ToString(exclude = "id", callSuper = true, includeFieldNames = true)
public class LombokDemo {
  private int id;
  private String name;
  private int age;
  public static void main(String[] args) {
      //输出LombokDemo(super=LombokDemo@48524010, name=null, age=0)
      System.out.println(new LombokDemo());
  }
}

@EqualsAndHashCode 示例

@EqualsAndHashCode(exclude = {"id", "shape"}, callSuper = false)
public class LombokDemo {
  private int id;
  private String shap;
}

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor 示例

@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor
public class LombokDemo {
  @NonNull
  private int id;
  @NonNull
  private String shap;
  private int age;
  public static void main(String[] args) {
      new LombokDemo(1, "circle");
      //使用静态工厂方法
      LombokDemo.of(2, "circle");
      //无参构造
      new LombokDemo();
      //包含所有参数
      new LombokDemo(1, "circle", 2);
  }
}

@Data 示例

import lombok.Data;
@Data
public class Menu {
  private String shopId;
  private String skuMenuId;
  private String skuName;
  private String normalizeSkuName;
  private String dishMenuId;
  private String dishName;
  private String dishNum;
  //默认阈值
  private float thresHold = 0;
  //新阈值
  private float newThresHold = 0;
  //总得分
  private float totalScore = 0;
}

@Value 示例

@Value
public class LombokDemo {
  @NonNull
  private int id;
  @NonNull
  private String shap;
  private int age;
  //相当于
  private final int id;
  public int getId() {
      return this.id;
  }
  ...
}

@Builder 示例

@Builder
public class BuilderExample {
  private String name;
  private int age;
  @Singular
  private Set<String> occupations;
  public static void main(String[] args) {
      BuilderExample test = BuilderExample.builder().age(11).name("test").build();
  }
}

@SneakyThrows 示例

import lombok.SneakyThrows;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
public class Test {
  @SneakyThrows()
  public void read() {
      InputStream inputStream = new FileInputStream("");
  }
  @SneakyThrows
  public void write() {
      throw new UnsupportedEncodingException();
  }
  //相当于
  public void read() throws FileNotFoundException {
      InputStream inputStream = new FileInputStream("");
  }
  public void write() throws UnsupportedEncodingException {
      throw new UnsupportedEncodingException();
  }
}

@Synchronized 示例

public class SynchronizedDemo {
  @Synchronized
  public static void hello() {
      System.out.println("world");
  }
  //相当于
  private static final Object $LOCK = new Object[0];
  public static void hello() {
      synchronized ($LOCK) {
          System.out.println("world");
      }
  }
}

@Getter(lazy = true)

public class GetterLazyExample {
  @Getter(lazy = true)
  private final double[] cached = expensive();
  private double[] expensive() {
      double[] result = new double[1000000];
      for (int i = 0; i < result.length; i++) {
          result[i] = Math.asin(i);
      }
      return result;
  }
}

// 相当于如下所示:

import java.util.concurrent.atomic.AtomicReference;
public class GetterLazyExample {
  private final AtomicReference<java.lang.Object> cached = new AtomicReference<>();
  public double[] getCached() {
      java.lang.Object value = this.cached.get();
      if (value == null) {
          synchronized (this.cached) {
              value = this.cached.get();
              if (value == null) {
                  final double[] actualValue = expensive();
                  value = actualValue == null ? this.cached : actualValue;
                  this.cached.set(value);
              }
          }
      }
      return (double[]) (value == this.cached ? null : value);
  }
  private double[] expensive() {
      double[] result = new double[1000000];
      for (int i = 0; i < result.length; i++) {
          result[i] = Math.asin(i);
      }
      return result;
  }
}

Lombok 注解原理(有点难懂,可忽略)

可能熟悉 Java 自定义注解的同学已经猜到,Lombok 这款插件正是依靠可插件化的 Java 自定义注解处理 API(JSR 269: Pluggable Annotation Processing API)来实现

在 Javac 编译阶段利用“Annotation Processor”,对自定义的注解进行预处理后生成真正在 JVM 上面执行的“Class文件”。有兴趣的同学反编译带有 Lombok 注解的类文件也就一目了然了。其大致执行原理图如下:

在这里插入图片描述

从上面的这个原理图上可以看出 Annotation Processing 是编译器在解析 Java 源代码和生成 Class 文件之间的一个步骤。其中 Lombok 插件具体的执行流程如下:

在这里插入图片描述

从上面的 Lombok 执行的流程图中可以看出,在 Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即 Lombok 自定义注解所需要生成的代码),最终通过分析生成 JVM 可执行的字节码 Class 文件。

使用 Annotation Processing 自定义注解是在编译阶段进行修改,而 JDK 的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。

需要更加深入理解 Lombok 插件的细节,自己查阅其源代码是必比可少的。对开源框架代码比较有执着追求的童鞋可以将 Lombok 的源代码工程从 github 上 download 到本地进行阅读和自己调试。下图为 Lombok 工程源代码的截图:
在这里插入图片描述

熟悉 JSR 269: Pluggable Annotation Processing API 的同学可以从工程类结构图中发现 AnnotationProcessor 这个类,是 Lombok自定义注解处理的入口。

该类有两个比较重要的方法一个是init方法,另外一个是process方法。

在init方法中,先用来做参数的初始化,将AnnotationProcessor类中定义的内部类(JavacDescriptor、EcjDescriptor)先注册到ProcessorDescriptor类型定义的列表中。其中,内部静态类—JavacDescriptor在其加载的时候就将 lombok.javac.apt.LombokProcessor 这个类进行对象实例化并注册。

在 LombokProcessor 处理器中,其中的process方法会根据优先级来分别运行相应的 handler 处理类。Lombok 中的多个自定义注解都分别有对应的 handler 处理类,如下图所示:
在这里插入图片描述

可以看出,在 Lombok 中对于其自定义注解进行实际的替换、修改和处理的正是这些handler类。对于其实现的细节可以具体参考其中的代码。

Lombok的优缺点

优点:

  1. 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
  2. 让代码变得简洁,不用过多的去关注相应的方法
  3. 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等

缺点:

  1. 不支持多种参数构造器的重载
  2. 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/132244.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!