SpringBoot中Lombok的使用规范介绍
Lombok 使用规范
一、项目信息
- Lombok 版本: 1.18.30
- Java 版本: 21
- Spring Boot 版本: 3.5.9
- MyBatis-Plus 版本: 3.5.15
二、Lombok 简介
-
Lombok 是什么?
- Lombok 是一个 Java 库,通过注解在编译时自动生成 getter、setter、toString、hashCode、equals 等样板代码。
-
为什么推荐使用?
-
减少代码量 60-70% - 自动生成常用方法,专注业务逻辑
-
提高可读性 - 代码更简洁,重点更突出
-
广泛使用 - Spring Boot、MyBatis-Plus 等主流框架都支持
-
-
常见疑虑解答
-
❓ Lombok 会影响性能吗?
✅ 不会。编译时生成代码,运行时与手写代码完全一致 -
❓ Lombok 是"黑魔法"吗?
✅ 不是。是标准的注解处理器,IDE 完全支持,可查看生成代码
- ❓ 学习成本高吗?
✅ 不高。本规范已总结所有要点,按规范使用即可
-
三、推荐使用的注解 ⭐
3.1 Entity 类
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@EqualsAndHashCode(callSuper = false) // 不继承父类时设为 false,虽然默认为false,显示声明是为了提高可读性,明确意图
@NoArgsConstructor // 必需:MyBatis-Plus、Jackson 反射需要(若使用了@Data、@Builder、@AllArgsConstructor、@RequiredArgsConstructor等注解时必须加,否则没有无参构造器)
@AllArgsConstructor // 推荐:全参构造器,方便测试、手动创建对象
@TableName("sys_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("username")
private String username;
@TableField("password")
@ToString.Exclude // 排除敏感字段
private String password;
}
-
说明:
-
@Data包含:@Getter、@Setter、@RequiredArgsConstructor、@ToString、@EqualsAndHashCode -
@Data默认是callSuper = false,显式写出可以提高代码可读性 -
如果 Entity 继承了
BaseEntity等父类,且父类属性需要参与对象比较,必须设置callSuper = true(见 5.3 节)
-
3.2 DTO/VO 类
- 使用示例:
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder // 推荐:用于 DTO,支持链式调用
@NoArgsConstructor // 必需:Jackson 反序列化需要(若使用了@Data、@Builder、@AllArgsConstructor、@RequiredArgsConstructor等注解时必须加,否则没有无参构造器)
@AllArgsConstructor // 推荐:@Builder 生成私有的全参构造器,这个生成公开的
public class UserRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@Builder.Default // 必需:使用 @Builder 时,默认值必须加此注解,否则默认值为 null
private Integer status = 1;
}
// 使用方式
UserRequest request = UserRequest.builder()
.username("alice")
.status(2)
.build();
- 等同于:
public class UserRequest {
private String username;
private Integer status;
// getter 方法
public String getUsername() {
return username;
}
public Integer getStatus() {
return status;
}
// setter 方法
public void setUsername(String username) {
this.username = username;
}
public void setStatus(Integer status) {
this.status = status;
}
// toString 方法
@Override
public String toString() {
return "UserRequest(username=" + username + ", status=" + status + ")";
}
// equals 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRequest that = (UserRequest) o;
return username.equals(that.username) &&
status.equals(that.status);
}
// hashCode 方法
@Override
public int hashCode() {
return Objects.hash(username, status);
}
// 无参构造器
public UserRequest() {}
// 全参构造器
public UserRequest(String username, Integer status) {
this.username = username;
this.status = status;
}
// builder 内部类
public static class UserRequestBuilder {
private String username;
private Integer status = 1; // 默认值
public UserRequestBuilder username(String username) {
this.username = username;
return this;
}
public UserRequestBuilder status(Integer status) {
this.status = status;
return this;
}
public UserRequest build() {
return new UserRequest(username, status);
}
}
// 静态 builder 方法
public static UserRequestBuilder builder() {
return new UserRequestBuilder();
}
}
3.3 Service 类
-
@RequiredArgsConstructor 的作用:
-
自动生成一个包含所有
final字段的构造器 -
Spring 自动通过这个构造器注入依赖
-
依赖字段必须是
final,确保不可变 -
替代
@Autowired,更清晰、更安全
-
-
使用示例:
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor // 推荐:替代 @Autowired,自动注入 final 依赖
public class UserService {
private final UserMapper userMapper; // 必须是 final,会被自动注入
private final EmailService emailService; // 必须是 final,会被自动注入
public void createUser(User user) {
log.info("Creating user: {}", user.getUsername());
userMapper.insert(user);
}
}
- 等同于:
@Service
public class UserService {
private final UserMapper userMapper;
private final EmailService emailService;
// @RequiredArgsConstructor 自动生成这个构造器
public UserService(UserMapper userMapper, EmailService emailService) {
this.userMapper = userMapper;
this.emailService = emailService;
}
}
-
优势:
-
不可变依赖:依赖字段是 final,不会被修改
-
编译期检查:缺少依赖时编译报错
-
便于测试:构造器注入,单元测试更简单
-
代码清晰:明确表示依赖关系,不需要 @Autowired
-
3.4 不可变类(推荐)
-
适用场景:配置类、常量类、DTO(不需要修改的对象)
-
@Value 的作用:
-
所有字段自动添加
final修饰符 -
自动生成全参构造器
-
自动生成 getter 方法
-
不生成 setter 方法(对象创建后不可修改)
-
自动生成
equals、hashCode、toString方法
-
-
使用示例:
import lombok.Value;
@Value // 所有字段 final,无 setter
public class AppConfig {
String appName;
Integer port;
@Builder.Default
Boolean debugMode = false;
}
// 使用
AppConfig config = new AppConfig("my-app", 8080, true);
System.out.println(config.getAppName()); // 可以读取
// config.setAppName("new"); // 编译错误,没有 setter 方法
- 等同于:
public class AppConfig {
private final String appName;
private final Integer port;
private final Boolean debugMode;
// getter 方法
public String getAppName() {
return appName;
}
public Integer getPort() {
return port;
}
public Boolean getDebugMode() {
return debugMode;
}
// 全参构造器
public AppConfig(String appName, Integer port, Boolean debugMode) {
this.appName = appName;
this.port = port;
this.debugMode = debugMode;
}
// toString 方法
@Override
public String toString() {
return "AppConfig(appName=" + appName + ", port=" + port + ", debugMode=" + debugMode + ")";
}
// equals 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AppConfig that = (AppConfig) o;
return appName.equals(that.appName) &&
port.equals(that.port) &&
debugMode.equals(that.debugMode);
}
// hashCode 方法
@Override
public int hashCode() {
return Objects.hash(appName, port, debugMode);
}
}
-
优势:
-
线程安全:不可变对象天然线程安全
-
防止修改:对象创建后状态不可变,避免意外修改
-
更简洁:一个注解替代多个注解
-
清晰明确:明确表示"这是不可变对象"
-
3.5 枚举类
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum UserStatus {
ACTIVE(1, "启用"),
INACTIVE(0, "禁用");
private final Integer code;
private final String desc;
}
四、禁止使用的注解 🚫
| 注解 | 原因 | 替代方案 |
|---|---|---|
@var / @val |
Java 10+ 支持原生 var |
使用 var |
@SneakyThrows |
隐藏异常,不利于维护 | 使用 try-catch 或 throws |
@Cleanup |
try-with-resources 更简洁 | 使用 try-with-resources |
@Synchronized |
可能导致死锁 | 使用 synchronized 或 ReentrantLock |
@NonNull |
推荐 JSR-303 规范 | 使用 @NotNull |
@ExtensionMethod |
官方不推荐 | 不使用 |
五、核心坑点与注意事项 ⚠️
5.1 @Builder 必须配合 @NoArgsConstructor 和 @AllArgsConstructor
- 原因:@Builder 会生成一个私有的全参构造器供内部使用,但 MyBatis-Plus、Jackson 等框架需要无参构造器,手动创建对象也需要公共全参构造器。
// ✅ 正确:三个注解配合使用
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {
private Long id;
private String name;
}
// ❌ 错误:缺少构造器注解
@Builder
public class Product {
private Long id;
private String name;
}
// 问题:无法通过反射创建对象,MyBatis-Plus 和 Jackson 会报错
5.2 @Builder.Default 必须添加
- 原因:@Builder 不会使用字段的默认值,必须显式添加 @Builder.Default 注解。
// ✅ 正确
@Builder
public class Product {
private Long id;
@Builder.Default // 必须添加
private Boolean isActive = true;
}
// ❌ 错误:默认值无效
@Builder
public class Product {
private Long id;
private Boolean isActive = true; // 默认值无效,会是 null
}
5.3 继承体系必须设置 callSuper = true
- 核心问题:
callSuper = false时,equals/hashCode 只比较当前类的字段,忽略父类的字段(如主键 id)。
// ✅ 正确:父类
@Data
@EqualsAndHashCode(callSuper = false) // BaseEntity 不继承其他类
public abstract class BaseEntity {
@TableId
private Long id; // 主键
@Version
private Integer version;
}
// ✅ 正确:子类必须设置 callSuper = true
@Data
@EqualsAndHashCode(callSuper = true) // 必须设为 true
@TableName("sys_user")
public class User extends BaseEntity {
private String username;
}
// 测试:此时会同时比较 id 和 username
User user1 = new User();
user1.setId(1L);
user1.setUsername("alice");
User user2 = new User();
user2.setId(2L); // id 不同
user2.setUsername("alice"); // username 相同
System.out.println(user1.equals(user2)); // false(正确!id 不同,对象不同)
// ❌ 错误:子类未设置 callSuper = true
@Data // 默认 callSuper = false
@TableName("sys_user")
public class User extends BaseEntity {
private String username;
}
// 测试:只比较 username,忽略父类的 id
User user1 = new User();
user1.setId(1L);
user1.setUsername("alice");
User user2 = new User();
user2.setId(2L); // id 不同,但被忽略
user2.setUsername("alice"); // username 相同
System.out.println(user1.equals(user2)); // true(严重错误!id 不同却判为相等)
-
危害:
-
数据库中 id 不同的记录可能被误判为同一个对象
-
Set/Map 集合可能因为误判而丢失数据
-
主键逻辑失效,导致数据一致性问题
-
5.4 避免循环引用
- 原因:双向关联调用 toString() 或 equals() 时会无限递归,导致 StackOverflowError。
// ✅ 正确:使用 ID 关联(推荐)
@Data
public class Order {
private Long id;
private Long userId; // 使用 ID 关联
}
// ❌ 错误:双向关联导致栈溢出
@Data
public class Order {
private Long id;
private User user; // User 中又包含 Order
}
// ✅ 正确:排除字段
@Data
@ToString.Exclude
@EqualsAndHashCode.Exclude
public class Order {
private Long id;
private User user;
}
5.5 敏感字段必须排除
- 原因:日志、调试、JSON 序列化时不应暴露密码等敏感信息,防止信息泄露。
import com.fasterxml.jackson.annotation.JsonIgnore;
@Data
public class User {
private String username;
@ToString.Exclude
@JsonIgnore
private String password; // 必须排除
}
5.6 构造器注解详解
- 核心原则:Java 只在类中没有显式声明任何构造器时,才会自动生成默认无参构造器。一旦使用了会生成构造器的注解,默认无参构造器就不再生成。
三个构造器注解对比
| 注解 | 生成什么构造器 | 参数 | 使用场景 |
|---|---|---|---|
@NoArgsConstructor |
无参构造器 | 无 | MyBatis-Plus、Jackson 反射需要 |
@RequiredArgsConstructor |
只包含 final 字段的构造器 | 所有 final 字段 | Service 依赖注入 |
@AllArgsConstructor |
全参构造器 | 所有字段 | 方便手动创建对象、测试 |
@Builder |
私有全参构造器 + builder 模式 | 所有字段 | 链式调用创建对象 |
为什么 Entity/DTO 必须加 @NoArgsConstructor?
- 原因 1:MyBatis-Plus 反射需要
// MyBatis-Plus 内部通过反射创建对象
public User selectById(Long id) {
User user = User.class.newInstance(); // 需要无参构造器
// ... 填充数据
return user;
}
- 原因 2:Jackson 反序列化需要
// Jackson 将 JSON 转为对象
String json = "{\"username\":\"alice\"}";
User user = objectMapper.readValue(json, User.class); // 需要无参构造器
- 原因 3:@Builder 或 @RequiredArgsConstructor 会阻止默认无参构造器
// ❌ 错误:没有无参构造器,MyBatis-Plus 和 Jackson 会报错
@Data
@Builder
public class User {
private String username;
}
// @Builder 生成了私有全参构造器,默认无参构造器不会生成
典型使用场景
- 场景 1:Entity 类
// ✅ 正确:Entity 需要 MyBatis-Plus 反射创建
@Data
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor // 必需:MyBatis-Plus 反射需要
@AllArgsConstructor // 推荐:方便测试、手动创建
@TableName("sys_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("username")
private String username;
}
// 使用方式:
User user1 = new User(); // 通过无参构造器
User user2 = new User(1L, "alice"); // 通过全参构造器
userMapper.insert(user1);
- 场景 2:DTO 类
// ✅ 正确:DTO 需要 Jackson 反序列化
@Data
@Builder
@NoArgsConstructor // 必需:Jackson 反序列化需要
@AllArgsConstructor // 推荐:@Builder 生成私有的,这个生成公开的
public class UserRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@Builder.Default
private Integer status = 1;
}
// 使用方式:
UserRequest request1 = UserRequest.builder() // 通过 builder
.username("alice")
.status(2)
.build();
UserRequest request2 = new UserRequest("bob", 1); // 通过全参构造器
UserRequest request3 = new UserRequest(); // 通过无参构造器(Jackson 会用)
- 场景 3:Service 类
// ✅ 正确:Service 只需要依赖注入,不需要无参构造器
@Slf4j
@Service
@RequiredArgsConstructor // 推荐:注入 final 依赖
public class UserService {
private final UserMapper userMapper; // 必须是 final
private final EmailService emailService; // 必须是 final
public void createUser(User user) {
log.info("Creating user: {}", user.getUsername());
userMapper.insert(user);
}
}
// Spring 会自动注入依赖,无需手动创建 Service 对象
关键要点
-
@Builder 的特殊性
-
@Builder 生成一个私有的全参构造器供内部使用
-
不影响其他构造器的生成
-
必须配合 @NoArgsConstructor(如果需要反射创建)
-
-
@RequiredArgsConstructor 与 Spring
-
只生成包含 final 字段的构造器
-
Spring 自动通过这个构造器注入依赖
-
不需要 @Autowired
-
-
可以同时使用多个构造器注解
-
Lombok 会生成所有构造器
-
互不冲突
-
提供多种创建对象的方式
-
-
何时省略 @AllArgsConstructor
-
如果只通过 @Builder 创建对象
-
如果只通过 @RequiredArgsConstructor 注入依赖
-
其他情况建议保留
-
六、MyBatis-Plus 集成注意事项
6.1 字段映射
-
驼峰命名 vs 下划线命名
- MyBatis-Plus 默认支持驼峰转下划线(如
userName→user_name) - 不需要额外配置,Lombok 生成的方法不受影响
- MyBatis-Plus 默认支持驼峰转下划线(如
-
自定义字段映射
@Data
@TableName("sys_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("user_name") // 显式指定数据库字段名
private String username;
}
6.2 逻辑删除字段
- 自动填充
@Data
@TableName("sys_user")
public class User {
private Long id;
private String username;
@TableLogic // 逻辑删除字段
private Integer deleted;
}
- 说明:Lombok 生成的 getter/setter 与 MyBatis-Plus 完全兼容
6.3 字段填充策略
- 自动填充创建/更新时间
@Data
@TableName("sys_user")
public class User {
@TableId
private Long id;
@TableField(fill = FieldFill.INSERT) // 创建时自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 创建和更新时填充
private LocalDateTime updateTime;
}
6.4 注意事项
- ✅ Entity 类必须有无参构造器(
@NoArgsConstructor) - ✅ 字段类型要与数据库类型匹配
- ✅ 逻辑删除字段使用
@TableLogic注解 - ✅ 版本字段使用
@Version注解(乐观锁)
七、IDE 及项目插件依赖配置
7.1 IntelliJ IDEA
-
安装 Lombok 插件(Plugins -> 搜索 Lombok -> Install)
-
启用注解处理器(Settings -> Compiler -> Annotation Processors -> 勾选 Enable annotation processing)
-
重启 IDEA
7.2 SpringBoot项目中插件依赖配置
<build>
<plugins>
<!-- Spring Boot Maven 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 排除 Lombok 依赖,因为它只在编译时需要 -->
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- Maven Surefire 插件,用于运行测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- 设置测试运行时的字符编码为UTF-8,防止测试日志中文乱码 -->
<argLine>-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8</argLine>
<forkCount>1</forkCount>
<reuseForks>false</reuseForks>
</configuration>
</plugin>
<!-- Maven 编译器插件,确保 Lombok 注解处理器正确工作 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
<fork>true</fork>
<compilerArgs>
<arg>-J-Dfile.encoding=UTF-8</arg>
<arg>-encoding</arg>
<arg>UTF-8</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

浙公网安备 33010602011771号