Lombok 实战与代码简化

本文介绍 Lombok 在 Java 项目中的实战用法,覆盖 Getter、Setter、ToString、EqualsAndHashCode、构造方法注解、Data、Builder、Slf4j 和 NonNull 等常用注解。文章重点说明 Lombok 如何减少 JavaBean 样板代码,以及在实体类、DTO、日志和构建对象时的使用边界。

第一章 Lombok 学习思路

1.1 Lombok 是什么

Lombok 是一个 Java 编译期代码生成工具。你在源码中添加注解,Lombok 会在编译时生成对应方法。

比如:

  • @Getter 生成 getter。
  • @Setter 生成 setter。
  • @ToString 生成 toString
  • @EqualsAndHashCode 生成 equalshashCode
  • @Builder 生成建造者模式代码。
  • @Slf4j 生成日志对象。

简单理解:Lombok 是帮你少写样板代码的代码生成器。

1.2 Lombok 到底做了什么

Lombok 不是运行时框架,它在编译阶段读取注解,然后生成字节码中需要的方法。你在源代码里看不到 getter/setter,但编译后的类里有这些方法。

使用 Lombok 前:

Java
public class User {
    private Long id;
    private String username;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

使用 Lombok 后:

Java
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class User {
    private Long id;
    private String username;
}

1.3 引入 Lombok

Maven 依赖示例:

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

provided 表示 Lombok 主要参与编译,不需要跟随程序一起打包运行。

1.4 IDEA 中启用 Lombok

新版 IntelliJ IDEA 通常已经内置 Lombok 支持。如果发现 @Data@Builder 等注解没有生效,可以检查:

  1. pom.xml 是否已经导入 Lombok 依赖。
  2. IDEA 是否开启 Annotation Processing:Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors
  3. Maven 是否刷新成功。

第二章 Getter 和 Setter

2.1 类级别使用

@Getter@Setter 可以写在类上,也可以写在字段上。写在类上时,会对类中的字段统一生成方法。

Java
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Product {
    private Long id;
    private String name;
    private Integer stock;
}

2.2 字段级别使用

如果只想给某个字段生成 getter 或 setter,可以把注解写在字段上。

Java
import lombok.Getter;
import lombok.Setter;

public class ProductQuery {
    @Getter
    @Setter
    private String keyword;

    @Getter
    private Integer pageSize = 10;
}

2.3 禁止生成某个方法

如果某个字段不希望生成 setter,可以使用 AccessLevel.NONE

Java
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Account {
    private Long id;
    private String username;

    @Setter(AccessLevel.NONE)
    private String password;
}

密码这类敏感字段,是否允许外部直接修改,要根据业务谨慎决定。


第三章 ToString

3.1 生成 toString

@ToString 用来生成 toString 方法,方便日志输出和调试。

Java
import lombok.ToString;

@ToString
public class Order {
    private Long id;
    private String orderNo;
    private Integer totalAmount;
}

3.2 排除敏感字段

如果对象中有密码、身份证号、手机号等敏感信息,不要直接输出:

Java
import lombok.ToString;

@ToString
public class LoginUser {
    private Long id;
    private String username;

    @ToString.Exclude
    private String password;
}

3.3 输出父类字段

如果类存在继承关系,可以根据需要设置是否输出父类字段:

Java
import lombok.ToString;

@ToString(callSuper = true)
public class AdminUser extends LoginUser {
    private String roleName;
}

是否输出父类字段取决于日志排查需要和敏感信息控制。


第四章 EqualsAndHashCode

4.1 基础用法

@EqualsAndHashCode 用来生成 equalshashCode。它常用于需要比较对象内容、放入 HashSet 或作为 HashMap key 的场景。

Java
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@EqualsAndHashCode
public class Category {
    private Long id;
    private String name;
}

4.2 指定参与比较的字段

实体类是否应该用全部字段参与比较,要根据业务决定。比如数据库实体通常更关注主键 id

Java
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class UserEntity {
    @EqualsAndHashCode.Include
    private Long id;

    private String username;
    private String nickname;
}

4.3 使用注意

如果对象会放进 HashSet 或作为 HashMap 的 key,不要随意修改参与 hashCode 计算的字段。否则对象放进去时和查找时的哈希值不同,可能导致找不到对象。


第五章 构造方法注解

5.1 常用构造注解

注解作用
@NoArgsConstructor生成无参构造
@AllArgsConstructor生成全参构造
@RequiredArgsConstructorfinal 字段和 @NonNull 字段生成构造
Java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    private String province;
    private String city;
    private String detail;
}

5.2 RequiredArgsConstructor 和构造器注入

在 Spring 项目中,@RequiredArgsConstructor 常用于构造器注入。

Java
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    /**
     * 根据用户编号查询用户名称。
     *
     * @param userId 用户编号
     * @return 用户名称
     */
    public String findUsername(Long userId) {
        return userRepository.findUsernameById(userId);
    }
}

相比字段注入,构造器注入更容易表达依赖是必需的,也更方便测试。


第六章 Data

6.1 Data 是什么

@Data 是一个组合注解,包含:

  • @Getter
  • @Setter
  • @ToString
  • @EqualsAndHashCode
  • @RequiredArgsConstructor
Java
import lombok.Data;

@Data
public class Student {
    private Long id;
    private String name;
    private Integer age;
}

6.2 Data 的风险

@Data 很省事,但不要在所有类上无脑使用。比如:

  • 有敏感字段的类,toString 可能泄露信息。
  • 复杂实体类,equalshashCode 可能不应该由所有字段决定。
  • 只读对象,不应该生成 setter。
  • 领域模型不希望外部随意修改字段。

实际项目中更推荐按需组合 @Getter@Setter@ToString@EqualsAndHashCode


第七章 Builder

7.1 Builder 基础用法

@Builder 可以生成建造者模式代码,适合字段多、构造参数多的对象。

Java
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
@Builder
public class CreateUserCommand {
    private String username;
    private String phone;
    private String email;
    private Integer age;
}

使用方式:

Java
public class BuilderDemo {
    public static void main(String[] args) {
        CreateUserCommand command = CreateUserCommand.builder()
                .username("wangwu")
                .phone("13800000000")
                .email("wangwu@example.com")
                .age(20)
                .build();

        System.out.println(command);
    }
}

7.2 Builder 默认值

如果字段有默认值,需要配合 @Builder.Default

Java
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class PageQuery {
    @Builder.Default
    private Integer pageNum = 1;

    @Builder.Default
    private Integer pageSize = 10;

    private String keyword;
}

如果没有 @Builder.Default,通过 builder 创建对象时字段默认值可能不会按你预期保留。

7.3 Builder 适合的场景

适合使用:

  • 创建参数很多的命令对象。
  • 创建不可变对象。
  • 测试中快速构造对象。
  • 避免构造方法参数顺序混乱。

不适合滥用:

  • 字段很少的简单对象。
  • 必须强制校验构造过程的复杂对象。
  • 需要非常明确构造语义的领域模型。

第八章 Slf4j

8.1 生成日志对象

@Slf4j 会生成一个名为 log 的日志对象。

Java
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PaymentService {
    /**
     * 模拟支付处理。
     *
     * @param orderNo 订单编号
     */
    public void pay(String orderNo) {
        log.info("开始处理支付,订单编号:{}", orderNo);

        // 关键业务逻辑:真实项目中这里会调用第三方支付接口,并根据返回结果更新订单状态。
        log.info("支付处理完成,订单编号:{}", orderNo);
    }
}

8.2 日志占位符

日志中推荐使用 {} 占位符,不要用字符串拼接。

Java
log.info("用户登录成功,userId:{}", userId);

这样日志级别关闭时,可以减少不必要的字符串拼接开销。

8.3 日志注意事项

日志中不要输出:

  • 明文密码。
  • 身份证号。
  • 完整手机号。
  • 银行卡号。
  • 访问令牌。

调试方便不能优先于数据安全。


第九章 NonNull

9.1 参数非空检查

@NonNull 可以给参数或字段生成非空检查。

Java
import lombok.NonNull;

public class RegisterService {
    /**
     * 注册用户。
     *
     * @param username 用户名,不能为空
     */
    public void register(@NonNull String username) {
        System.out.println("注册用户:" + username);
    }
}

如果调用 register(null),会抛出空指针异常。它适合快速防御简单参数,但复杂校验还是要写清楚业务规则。

9.2 和业务校验的区别

@NonNull 只能判断是不是 null,不能判断:

  • 字符串是否为空白。
  • 手机号格式是否正确。
  • 年龄是否在合理范围内。
  • 用户名是否已经存在。

所以它适合基础防御,不适合替代业务校验。


第十章 Lombok 分层使用建议

10.1 DTO 和 VO

DTO、VO 通常用于数据传输,可以适当使用 Lombok。

Java
import lombok.Data;

@Data
public class UserVO {
    private Long id;
    private String username;
    private String nickname;
}

如果有敏感字段,避免直接用 @Data,或者使用 @ToString.Exclude 排除输出。

10.2 Entity

数据库实体类要谨慎使用 @Data,因为 equalshashCodetoString 都可能带来隐患。

更稳妥的写法:

Java
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UserEntity {
    private Long id;
    private String username;

    @ToString.Exclude
    private String password;
}

10.3 Service

Service 中常用 @RequiredArgsConstructor@Slf4j

Java
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;

    /**
     * 根据订单编号查询订单。
     *
     * @param orderNo 订单编号
     * @return 订单对象
     */
    public Order findByOrderNo(String orderNo) {
        log.info("查询订单,orderNo:{}", orderNo);
        return orderRepository.findByOrderNo(orderNo);
    }
}

第十一章 使用建议和常见坑

11.1 推荐使用方式

建议说明
按需使用注解不要所有类都直接上 @Data
敏感字段排除输出密码、手机号、身份证号不要进入 toString
实体类谨慎生成 equals数据库实体的相等规则要和业务保持一致
团队统一规范明确哪些层可以用 @Data、哪些层推荐 @Getter
构建环境要支持CI、IDEA、Maven 都要能正确处理注解

11.2 不建议使用 Lombok 的场景

  • 团队成员不熟悉 Lombok,排查问题成本很高。
  • 项目对可读源码要求极高,希望所有方法都显式写出来。
  • 某些框架或构建插件与 Lombok 兼容性不好。
  • 领域模型行为复杂,不希望外部随意修改字段。

这不是说 Lombok 不好,而是要根据项目阶段和团队习惯选择。

11.3 常见问题排查

如果 Lombok 注解不生效,可以检查:

  1. Maven 依赖是否导入成功。
  2. IDEA 是否开启 Annotation Processing。
  3. Maven 是否刷新。
  4. 项目 JDK 和编译插件版本是否匹配。
  5. CI 环境是否使用了正确的 Maven 和 JDK。

总结

Lombok 通过注解减少 getter、setter、构造方法、toStringequalshashCode、Builder、日志对象等样板代码。

它的正确使用方式不是“所有类都加 @Data”,而是根据类的职责按需选择注解。对于 DTO、VO,可以适当简化;对于 Entity、领域模型和安全敏感对象,要更谨慎。用得好,Lombok 能让代码清爽很多;用得太随意,也会把一些本该显式表达的业务规则藏起来。