Lombok —— 简介
官网地址:Project Lombok
1、什么是 Lombok
官网介绍:
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.Project Lombok 是一个 java 库,可自动插入您的编辑器和构建工具,为您的 java 增添趣味。
永远不要再编写另一个 getter 或 equals 方法,使用一个注释,您的类就有一个功能齐全的构建器、自动化您的日志记录变量等等。
Lombok 是一种 JavaTM 实用工具,可用来帮助开发人员消除 Java 中的冗长代码,尤其是对于简单的 Java 对象(POJO)/ JavaBean,它通过注解实现这一目的
简而言之:Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率
使用前后对比
-
使用前
package cn.edu.hziee.pojo; public class User { private String userName; private String password; private Integer age; private String phone; private String[] interest; private Boolean isMale; public User() { } public User(String userName, String password, Integer age, String phone, String[] interest, Boolean isMale) { this.userName = userName; this.password = password; this.age = age; this.phone = phone; this.interest = interest; this.isMale = isMale; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String[] getInterest() { return interest; } public void setInterest(String[] interest) { this.interest = interest; } public Boolean getMale() { return isMale; } public void setMale(Boolean male) { isMale = male; } }
-
使用 Lombok 后
package cn.edu.hziee.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User { private String userName; private String password; private Integer age; private String phone; private String[] interest; private Boolean isMale; }
2、Lombok 原理
Lombok本质上就是一个实现了“JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下:
- javac 对源代码进行分析,生成了一棵抽象语法树(AST)
- 运行过程中调用实现了“JSR 269 API”的Lombok程序
- 此时 ombok 就对第一步骤得到的AST进行处理,找到 @Data 注解所在类对应的语法树(AST),然后修改该语法树(AST),增加 getter 和 setter 方法定义的相应树节点
- javac 使用修改后的抽象语法树(AST)生成字节码文件,即给 class 增加新的节点(代码块)
通过读Lombok源码,发现对应注解的实现都在HandleXXX中,比如@Getter注解的实现在HandleGetter.handle()。还有一些其它类库使用这种方式实现,比如Google Auto、Dagger等等
JSR 269:插件化注解处理API(Pluggable Annotation Processing API)
Lombok 的核心之处就是对于注解的解析上。JDK5引入了注解的同时,也提供了两种解析方式
-
编译阶段处理
@Retention(RetentionPolicy.SOURCE)
编译时解析有两种机制,分别简单描述下:
-
Annotation Processing Tool
apt 自 JDK5 产生,JDK7 已标记为过期,不推荐使用,JDK8 中已彻底删除,自 JDK6 开始,可以使用 Pluggable Annotation Processing API 来替换它,apt 被替换主要有2点原因:
- api都在com.sun.mirror非标准包下
- 没有集成到javac中,需要额外运行
-
Pluggable Annotation Processing API
JSR 269 自 JDK6 加入,作为 apt 的替代方案,它解决了 apt 的两个问题,javac 在执行的时候会调用实现了该 API 的程序,这样我们就可以对编译器做一些增强,javac 执行的过程如下:
-
-
运行阶段处理
@Retention(RetentionPolicy.RUNTIME)
运行时能够解析的注解,必须将 @Retention 设置为 RUNTIME ,这样就可以通过反射拿到该注解。 java.lang.reflect 反射包中提供了一个接口 AnnotatedElement ,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package 等都实现了该接口,对反射熟悉的朋友应该都会很熟悉这种解析方式Lombok 注解在编译阶段就会生效,在编译生成的 .class 文件中生成相应的方法
例如:
-
加有 Lombok 注解的 .java 文件
package cn.edu.hziee.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User { private String userName; private String password; private Integer age; private String phone; private String[] interest; private Boolean isMale; }
-
编译生成的 .class 文件
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package cn.edu.hziee.pojo; import java.util.Arrays; public class User { private String userName; private String password; private Integer age; private String phone; private String[] interest; private Boolean isMale; public String getUserName() { return this.userName; } public String getPassword() { return this.password; } public Integer getAge() { return this.age; } public String getPhone() { return this.phone; } public String[] getInterest() { return this.interest; } public Boolean getIsMale() { return this.isMale; } public void setUserName(String userName) { this.userName = userName; } public void setPassword(String password) { this.password = password; } public void setAge(Integer age) { this.age = age; } public void setPhone(String phone) { this.phone = phone; } public void setInterest(String[] interest) { this.interest = interest; } public void setIsMale(Boolean isMale) { this.isMale = isMale; } public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof User)) { return false; } else { User other = (User)o; if (!other.canEqual(this)) { return false; } else { label75: { Object this$age = this.getAge(); Object other$age = other.getAge(); if (this$age == null) { if (other$age == null) { break label75; } } else if (this$age.equals(other$age)) { break label75; } return false; } Object this$isMale = this.getIsMale(); Object other$isMale = other.getIsMale(); if (this$isMale == null) { if (other$isMale != null) { return false; } } else if (!this$isMale.equals(other$isMale)) { return false; } Object this$userName = this.getUserName(); Object other$userName = other.getUserName(); if (this$userName == null) { if (other$userName != null) { return false; } } else if (!this$userName.equals(other$userName)) { return false; } label54: { Object this$password = this.getPassword(); Object other$password = other.getPassword(); if (this$password == null) { if (other$password == null) { break label54; } } else if (this$password.equals(other$password)) { break label54; } return false; } label47: { Object this$phone = this.getPhone(); Object other$phone = other.getPhone(); if (this$phone == null) { if (other$phone == null) { break label47; } } else if (this$phone.equals(other$phone)) { break label47; } return false; } if (!Arrays.deepEquals(this.getInterest(), other.getInterest())) { return false; } else { return true; } } } } protected boolean canEqual(Object other) { return other instanceof User; } public int hashCode() { int PRIME = true; int result = 1; Object $age = this.getAge(); int result = result * 59 + ($age == null ? 43 : $age.hashCode()); Object $isMale = this.getIsMale(); result = result * 59 + ($isMale == null ? 43 : $isMale.hashCode()); Object $userName = this.getUserName(); result = result * 59 + ($userName == null ? 43 : $userName.hashCode()); Object $password = this.getPassword(); result = result * 59 + ($password == null ? 43 : $password.hashCode()); Object $phone = this.getPhone(); result = result * 59 + ($phone == null ? 43 : $phone.hashCode()); result = result * 59 + Arrays.deepHashCode(this.getInterest()); return result; } public String toString() { return "User(userName=" + this.getUserName() + ", password=" + this.getPassword() + ", age=" + this.getAge() + ", phone=" + this.getPhone() + ", interest=" + Arrays.deepToString(this.getInterest()) + ", isMale=" + this.getIsMale() + ")"; } public User() { } public User(String userName, String password, Integer age, String phone, String[] interest, Boolean isMale) { this.userName = userName; this.password = password; this.age = age; this.phone = phone; this.interest = interest; this.isMale = isMale; } }
3、Lombok 安裝
添加 maven 依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
官网说明:
The Jetbrains IntelliJ IDEA editor is compatible with lombok without a plugin as of version 2020.3.
For versions prior to 2020.3, you can add the Lombok IntelliJ plugin to add lombok support for IntelliJ:
- Go to
File > Settings > Plugins
- Click on
Browse repositories...
- Search for
Lombok Plugin
- Click on
Install plugin
- Restart IntelliJ IDEA
4、Lombok的优缺点
Lombok 注解可以自动生成代码,大大减少了代码量,使代码非常简洁。
但是并不意味着Lombok的使用没有任何问题,在使用 Lombok 的过程中,还可能存在对队友不友好、对代码不友好、对调试不友好、对升级不友好等问题。
虽然,使用 Lombok 还会导致破坏封装性的问题,但是我更认为 Lombok 的操作是遵循了Bean 的使用初衷。
Bean 尤其数据库和Java类的映射 Bean,Java 对 Bean 的定义和使用就是无参数的构造方法和 set 和 get 方法,而不应该在 bean 中处理任何和业务有任何关系的逻辑
4.1、Lombok 优点
使用 @Data 注解大大减少了代码量,使代码非常简洁,这也是很多开发者热衷于使用 Lombok 的主要原因。
不仅如此,Lombok其它的优势:
- 减少模板代码:Lombok 处理 get,set,toString,hash,equal 等方法,大量的模板代码进行封装,减少重复代码,当增加新属性的时候,以上方法都不需要再重新编写
- 增强代码可读性:专注于类的属性定义,不需要再去为排版浪费时间
- 减少代码维护:新增属性的时候,会减少非常多的代码维护工作
4.2、Lombok 缺点
-
强迫队友
Lombok 插件的使用,要求开发者一定要在 IDE 中安装对应的插件。不仅自己要安装,任何和你协同开发的人都要安装。
如果有谁未安装插件的话,使用IDE打开一个基于 Lombok 的项目的话会提示找不到方法等错误,导致项目编译失败。
更重要的是,如果我们定义的一个jar包中使用了 Lombok ,那么就要求所有依赖这个 jar 包的所有应用都必须安装插件,这种侵入性是很高的。
只次一点,我就已经决定不在自己的代码中使用 Lombok 注解了,但是,为了项目编译我依然会使用 Lombok 插件
-
代码可调试性降低
Lombok 确实可以帮忙减少很多代码,因为 Lombok 会帮忙自动生成很多代码。
但是,这些代码是要在编译阶段才会生成的,所以在开发的过程中,其实很多代码其实是缺失的。
这就给代码调试带来一定的问题,我们想要知道某个类中的某个属性的getter方法都被哪些类引用的话,就没那么简单了。
-
影响版本升级
Lombok 对于代码有很强的侵入性,就可能带来一个比较大的问题,那就是会影响我们对 JDK 的升级。
按照如今 JDK 的升级频率,每半年都会推出一个新的版本,但是 Lombok 作为一个第三方工具,并且是由开源团队维护的,那么他的迭代速度是无法保证的
所以,如果我们需要升级到某个新版本的 JDK 的时候,若其中的特性在 Lombok 中不支持的话就会受到影响。
还有一个可能带来的问题,就是 Lombok 自身的升级也会受到限制。
因为一个应用可能依赖了多个 jar 包,而每个jar包可能又要依赖不同版本的 Lombok,这就导致在应用中需要做版本仲裁,而我们知道,jar 包版本仲裁是没那么容易的,而且发生问题的概率也很高
-
更容易产生错误
在使用 Lombok 过程中,如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果。
举一个简单的例子:
我们知道,当我们使用 @Data 定义一个类的时候,会自动帮我们生成 equals() 方法 。
但是如果只使用了 @Data,而不使用 @EqualsAndHashCode(callSuper=true) 的话,会默认是 @EqualsAndHashCode(callSuper=false),这时候生成的 equals() 方法只会比较子类的属性,不会考虑从父类继承的属性,无论父类属性访问权限是否开放,这就可能得到意想不到的结果。
-
可能会破坏封装性
如果说上面的4点问题都可以人为避免,那么,关于封装性的问题就是Lombok的短板了。
举个简单的例子,我们定义一个购物车类:
@Data public class ShoppingCart { //商品数目 private int itemsCount; //总价格 private double totalPrice; //商品明细 private List items = new ArrayList<>(); }
我们知道,购物车中商品数目、商品明细以及总价格三者之前其实是有关联关系的,如果需要修改的话是要一起修改的
但是,我们使用了 Lombok 的 @Data 注解,对于 itemsCount 和 totalPrice 这两个属性,虽然我们将它们定义成 private 类型,但是提供了 public 的 getter、setter 方法
外部可以通过 setter 方法随意地修改这两个属性的值,我们可以随意调用 setter 方法,来重新设置 itemsCount、totalPrice 属性的值,这也会导致其跟 items 属性的值不一致
而面向对象封装的定义是:通过访问权限控制,隐藏内部数据,外部仅能通过类提供的有限的接口访问、修改内部数据。所以,暴露不应该暴露的 setter 方法,明显违反了面向对象的封装特性
好的做法应该是不提供 getter/setter,而是只提供一个 public 的 addItem 方法,同时取修改 itemsCount、totalPrice 以及 items 三个属性