Lombok注解

44 阅读4分钟

Lombok 是一个 Java 库,通过在编译期自动生成样板代码(如 getter、setter、构造函数等),显著减少 Java 项目中的冗余代码。其核心原理基于 Java 的注解处理器(Annotation Processor)JSR 269 编译插件机制

一、Lombok 注解使用原理

1. 编译期代码生成(非运行时)

Lombok 并不是通过反射或字节码增强在运行时修改类,而是在 javac 编译阶段 拦截 AST(抽象语法树),直接修改源代码结构,插入所需方法。

  • 关键机制:Lombok 实现了 javax.annotation.processing.Processor 接口,并通过 META-INF/services/javax.annotation.processing.Processor 注册。

  • 工作流程

    1. 开发者编写带 Lombok 注解的 Java 源码(如 @Data)。
    2. javac 编译器在解析源码生成 AST 后,调用 Lombok 的注解处理器。
    3. Lombok 修改 AST(例如添加 getter/setter 方法节点)。
    4. 编译器继续编译“已增强”的 AST,生成最终的 .class 文件。
    5. 运行时完全感知不到 Lombok 的存在——因为生成的字节码已经包含所有方法。

✅ 优势:零运行时开销,兼容所有 JVM;

⚠️ 注意:IDE 需要安装 Lombok 插件才能正确识别生成的方法(否则会报红)。

源代码 → Java编译器(javac) → 注解处理器 → 修改AST → 生成字节码

2. 常见注解及其作用

注解作用
@Getter / @Setter自动生成字段的 getter/setter 方法
@ToString生成 toString() 方法
@EqualsAndHashCode生成 equals() 和 hashCode()
@Data等价于 @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor
@NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor生成无参、全参、或仅 final/非空字段的构造函数
@Builder实现建造者模式(Builder Pattern)
@Slf4j / @Log4j2 等自动生成日志对象(如 private static final Logger log = LoggerFactory.getLogger(...)

二、使用注意事项(避坑指南)

1. IDE 支持必须开启

  • IntelliJ IDEA:需安装 Lombok Plugin 并启用 Annotation Processing(Settings → Build → Compiler → Annotation Processors → Enable)。
  • Eclipse:需运行 lombok.jar 安装到 IDE(会修改 eclipse.ini)。

❌ 否则 IDE 无法识别生成的方法,导致编译错误提示(但实际能编译通过)。


2. 依赖配置

<!-- Maven -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

3. 常见问题与陷阱

  • JPA / Hibernate 实体类

    • 使用 @Data 可能导致 equals/hashCode 问题(因懒加载代理对象比较失败)。
    • 建议:实体类慎用 @Data,改用手动写 @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) 并显式标注参与比较的字段。
  • Jackson / JSON 序列化

    • 若字段为 final 且无 setter,反序列化可能失败(需配合 @JsonCreator 或使用 @AllArgsConstructor + @Builder)。
  • 记录类(Record) :Java 14+ 的 record 已内置类似功能,无需 Lombok。

  • 序列化兼容性:反序列化时可能丢失默认值

// @Data的隐患
@Data
@Entity
public class User {
    @Id
    private Long id;
    private String name;
    // 问题1:生成equals/hashCode包含所有字段,影响JPA性能
    // 问题2:无参构造可能被覆盖
}

// 解决方案
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class User {
    @Id
    @EqualsAndHashCode.Include
    private Long id;
    private String name;
}

// 构造器注解冲突
@AllArgsConstructor
@NoArgsConstructor
public class Example {
    private final String id;  // 编译错误:final字段需要初始化
    
    // 解决方案:使用@RequiredArgsConstructor替代
}

// 继承问题
@Data
public class Parent {
    private String parentField;
}

@Data
public class Child extends Parent {
    private String childField;
    // 问题:不会包含父类的toString/equals/hashCode
}

// 解决方案
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class Child extends Parent {
    private String childField;
}

// 循环依赖
@Data
public class A {
    private B b;
}

@Data
public class B {
    private A a;  // 问题:toString/equals/hashCode可能导致栈溢出
}

// 序列化兼容性
@Data
@Builder
public class SerializableClass implements Serializable {
    private Long id;
    @Builder.Default
    private String name = "default";  // 反序列化时可能丢失默认值
    
    // 解决方案:添加readResolve方法
    private Object readResolve() {
        if (name == null) name = "default";
        return this;
    }
}

// 与其他框架集成
@Entity
@Data
@NoArgsConstructor
public class JpaEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // @Data生成setter可能导致业务逻辑绕过
    // 考虑使用@Setter(AccessLevel.PROTECTED)
}

三、性能考虑

  • 编译时间:增加编译时间(特别是大型项目)
  • 反射影响:某些注解(如@Builder.Default)可能使用反射

四、最佳实践

1. 精确使用注解

// 不推荐:过度使用@Data
@Data
public class User {
    private Long id;
    private String name;
}

// 推荐:按需使用
@Getter
@Setter(AccessLevel.PROTECTED)  // 保护性setter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
    @EqualsAndHashCode.Include
    private Long id;
    private String name;
}

2. 不可变对象设计

@Value  // 所有字段private final,生成全参构造
@Builder
public class ImmutableObject {
    Long id;
    String name;
}

3. 测试考虑

// Lombok生成的代码可能影响测试覆盖率
// 解决方案:配置Jacoco忽略Lombok生成的方法
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <configuration>
        <excludes>
            <exclude>**/*$Lombok</exclude>
        </excludes>
    </configuration>
</plugin>

4. 常见错误排查

  • 找不到符号错误:检查IDE Lombok插件
  • 序列化版本不一致:添加serialVersionUID
  • 默认值不生效:检查@Builder.Default使用