Lombok 学习实践
一、Lombok 简介
Lombok 是一个 Java 库,能自动插入到编辑器和构建工具中,简化 Java 开发。通过添加注解的方式,不再需要为类编写 getter/setter、equals、hashCode、toString 等方法,同时可以自动生成日志变量。
简而言之:Lombok 能以简单的注解形式来简化 Java 代码,提高开发人员的开发效率。
二、Lombok 的使用
使用 Lombok 需要的开发环境:Java + Maven + IntelliJ IDEA 或 Eclipse。
2.1 添加 Maven 依赖
推荐写法(Spring Boot 项目,版本交给 BOM 统一管理):
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>非 Spring Boot 的普通 Maven 项目需要显式指定版本:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>关于版本:上面的 1.18.4 发布于 2018 年,已经明显落后。Lombok 与 JDK 版本强绑定,旧版本在新 JDK 上会直接编译失败 —— 支持 JDK 17 需要 1.18.22 以上,支持 JDK 21 需要 1.18.30 以上。新项目请使用当前较新的版本,不要照抄这个数字。
scope 写 provided 或 optional 都可以,本质是不要让 Lombok 传递到下游依赖,因为它只在编译期起作用。
2.2 安装插件
Lombok 需要 IDE 插件的配合,否则 IDE 会误报「找不到 getter/setter 方法」。这里以 IDEA 为例说明:
IntelliJ IDEA 2020.3 及以上:Lombok 插件已内置,无需手动安装,装好即可用。
更早版本:打开 IDEA 设置 →
Plugins→Browse repositories,在弹出窗口中搜索lombok,然后安装并重启即可。
使用 Eclipse、MyEclipse 的请自行查阅对应的安装方法。
2.3 解决编译时出错问题
如果编译报错、提示找不到符号,通常是没有开启注解处理器。路径为:
Settings → Build, Execution, Deployment → Compiler → Annotation Processors
勾选 Enable annotation processing设置完成之后程序即可正常运行。注意:新版 IDEA 默认已开启该项,如果被手动关掉,反而会出现「代码没红但是编译不过」的怪现象。
2.4 示例:用与不用的区别
创建一个用户类,先看看不使用 Lombok 的写法:
import java.io.Serializable;
import java.util.Objects;
public class User implements Serializable {
private static final long serialVersionUID = -8054600833969507380L;
private Integer id;
private String username;
private Integer age;
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(username, user.username) &&
Objects.equals(age, user.age);
}
@Override
public int hashCode() {
return Objects.hash(id, username, age);
}
}使用 Lombok 之后,等价的代码只有这么几行:
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private static final long serialVersionUID = -8054600833969507380L;
private Integer id;
private String username;
private Integer age;
}近 60 行样板代码被压缩成 4 个字段声明。需要强调的是:生成的代码只存在于字节码中,源码里看不到,可以用 javap -p User.class 反编译验证,getter/setter/equals/hashCode 都真实存在。
2.5 常用注解
除此之外,还有一些日常很常用的注解:
三、Lombok 的工作原理
在使用 Lombok 的过程中,只需要添加相应的注解,无需再为此写任何代码。自动生成的代码到底是如何产生的呢?核心之处就是对于注解的解析。
JDK 5 引入注解的同时,也提供了两种解析方式。
3.1 运行时解析
运行时能够解析的注解,必须将 @Retention 设置为 RUNTIME,这样就可以通过反射拿到该注解。java.lang.reflect 反射包中提供了一个接口 AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package 等都实现了该接口,对反射熟悉的朋友应该都很熟悉这种解析方式。
但 Lombok 走的不是这条路 —— 它的注解保留策略是 SOURCE,连字节码里都不保留。
3.2 编译时解析
编译时解析有两种机制:
Annotation Processing Tool(apt):自 JDK 5 产生,JDK 7 已标记为过期、不推荐使用,JDK 8 中已彻底删除。自 JDK 6 开始,可以使用 Pluggable Annotation Processing API 来替换它。apt 被替换主要有 2 点原因:
API 都在
com.sun.mirror非标准包下;没有集成到 javac 中,需要额外运行。
Pluggable Annotation Processing API:JSR 269 自 JDK 6 加入,作为 apt 的替代方案。它解决了 apt 的上述两个问题 —— javac 在执行的时候会调用实现了该 API 的程序,这样我们就可以对编译器做一些增强。
3.3 Lombok 的具体流程
Lombok 本质上就是一个实现了「JSR 269 API」的程序。在使用 javac 的过程中,它产生作用的具体流程如下:
javac 对源代码进行分析,生成一棵抽象语法树(AST);
运行过程中调用实现了「JSR 269 API」的 Lombok 程序;
此时 Lombok 就对第一步得到的 AST 进行处理,找到
@Data注解所在类对应的语法树(AST),然后修改该语法树,增加 getter 和 setter 方法定义的相应树节点;javac 使用修改后的抽象语法树(AST)生成字节码文件,即给 class 增加新的节点(代码块)。
这正是 Lombok 与其他注解库最大的不同:它不是「读取注解做点什么」,而是篡改编译器正在处理的语法树。这也是为什么它需要 IDE 插件配合 —— IDE 有自己的编译器,不装插件就无法看到这些凭空多出来的方法。
通过阅读 Lombok 源码可以发现,对应注解的实现都在 HandleXXX 中,比如 @Getter 注解的实现在 HandleGetter.handle()。还有一些其他类库也使用这种方式实现,比如 Google Auto、Dagger 等等。
四、Lombok 的优缺点
优点
能通过注解的形式自动生成构造器、getter/setter、equals、hashCode、toString 等方法,提高了一定的开发效率;
让代码变得简洁,不用过多地去关注相应的方法;
属性做修改时,也简化了维护为这些属性所生成的 getter/setter 方法等。
缺点
不支持多种参数构造器的重载;
虽然省去了手动创建 getter/setter 方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度;
与 JDK、IDE 版本强绑定,升级 JDK 时常常需要同步升级 Lombok,否则编译直接失败;
依赖注解处理器,脱离 IDE 用命令行构建、或团队中有人未开启注解处理时,容易出现「我这能编译、你那不能」的差异。
五、常见坑
1. @Data 的 equals/hashCode 不含父类字段
这是最高频的陷阱。子类继承父类后,@Data 生成的 equals 只比较本类字段,两个「父类字段不同」的对象可能被判为相等。需要显式声明:
@Data
@EqualsAndHashCode(callSuper = true)
public class SubUser extends User {
private String nickname;
}IDEA 会对此给出警告,不要顺手把它关掉。
2. 实体类与 JPA / MyBatis 的配合
JPA 实体要求有无参构造器,而 @Builder 默认需要全参构造器,两者同时使用时要显式补上:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
}3. 构造器注入与 Spring 的搭配
@RequiredArgsConstructor 配合 final 字段是 Spring 官方推荐的注入方式,可以省掉 @Autowired,也让依赖一目了然:
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
private final RedisTemplate<String, Object> redisTemplate;
public User getById(Integer id) {
log.info("query user, id={}", id);
return userMapper.selectById(id);
}
}4. 序列化框架的兼容性
部分序列化框架(如某些版本的 Jackson、Fastjson)依赖 getter 或字段可见性推导属性名。使用 Lombok 后属性名由生成的方法决定,遇到「字段名与 JSON key 不一致」时,优先检查 getter 的命名规则,必要时用 @JsonProperty 显式标注。
5. 版本升级要一步到位
升级 JDK 时如果报出大量「找不到符号」「无法访问 xxx」的错误,先确认 Lombok 版本是否匹配新 JDK,而不是去怀疑业务代码。
六、小结
Lombok 用注解替代样板代码,核心价值是消除 getter/setter/equals/hashCode/toString 的重复劳动。
使用前提有三:加对依赖(
provided或optional)、装好 IDE 插件(IDEA 2020.3+ 已内置)、开启注解处理器。它的原理是 JSR 269 注解处理器在编译期修改 AST,因此生成的代码只在字节码中存在。
用之前务必知道
@Data的equals/hashCode不含父类字段这个坑,继承场景要加@EqualsAndHashCode(callSuper = true)。别把版本号写死在文档里照抄 —— Lombok 与 JDK 版本强绑定,新项目交给 Spring Boot BOM 管理即可。
评论区