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 方法(对象创建后不可修改)

    • 自动生成 equalshashCodetoString 方法

  • 使用示例

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-catchthrows
@Cleanup try-with-resources 更简洁 使用 try-with-resources
@Synchronized 可能导致死锁 使用 synchronizedReentrantLock
@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 默认支持驼峰转下划线(如 userNameuser_name
    • 不需要额外配置,Lombok 生成的方法不受影响
  • 自定义字段映射

@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>

八、代码审查检查清单


posted @ 2026-01-23 22:42  flycloudy  阅读(9)  评论(0)    收藏  举报