Lombok 及常用注解

摘要:Java 语言增强库,简化 POJOs 实体类封装。通过为实体类添加注解、来自动生成(并代替)通用方法;减少冗余代码、提升开发效率。


目录

[TOC]

Lombok

Lombok (音lao母报ke)插件:Java 语言增强库,简化 POJOs 实体类封装。通过为实体类添加注解、来自动生成(并代替)通用方法;减少冗余代码、提升开发效率。

  • 用途:一般用于简单、属性比较多的 POJOs (子)实体类、DTO 数据传输对象、请求参数 ReqVO 或返回结果实体类 RespVO。
  • 缺点:提高阅读源码的门槛,继承父类时 equals() 可能会出错。

原理:在编译器编译时通过操作 AST(抽象语法树)改变字节码生成。即改变编写源码的方式,是编译时的特性,不像 Spring 的依赖注入一样是运行时的特性

  • 在项目的根目录有 lombok.config (opens new window)全局配置文件,开启链式调用、生成的 toString/hashcode/equals 方法需要调用父方法。

@Data、@Value

  1. @Data:用在类上,相当于同时使用 @Getter / @Setter@ToString@EqualsAndHashCode@RequiredArgsConstructor5个注解的组合,生成通常与简单POJO关联的所有样板和 bean
    • final 修饰的属性,添加 @Setter 注解的效果。
    • final 修饰的属性,添加 @RequiredArgsConstructor 注解的效果。
    • 因为包含 @EqualsAndHashcode() 所以也有相同的问题。

不过要注意,如果使用 @Data 注解的类,继承成了其它父类的属性,最好额外添加 @ToString(callSuper = true)@EqualsAndHashCode(callSuper = true) 注解。

  • 因为默认情况下,@Data 注解不会处理父类的属性。所以需要我们通过 callSuper = true 属性,声明需要调用父类对应的方法。
  • 这个情况非常常见,例如说在实体类中,可能会声明一个抽象父类 AbstractEntity,而该类里有一个 id 编号属性。
  1. @Value:用在类上,和 @Data类似,区别在于所有成员变量默认定义为 private final 修饰,且不生成 setter() 方法
    • 与 Spring 中用于绑定 @Configuration 配置的注解 @Value 重名,注意 import 路径;

@EqualsAndHashCode

@EqualsAndHashCode(callSuper=false):用在类上,自动生成 equals() 方法和 hashCode() 方法,包括所有非静态变量和非 transient 的变量。

  1. 问题:默认 callSuper=false。默认仅使用子类中定义的属性、不调用父类的方法,可能会导致比较对象时出错;
    1. 如,有多个类有相同的属性(如 id),把它们提取到父类中作为共同属性;
    2. 因为子类仅比较当前对象的属性、而不比较父类中的 id,所以子类对象各属性相等时其父类 id 可能并不相同。
    3. lombok 自动生成equals()hashCode() 方法返回 true,判定为相等,但实际上并不相等,从而导致出错。
  2. 修复方法
    1. 在使用 @Data 的同时,加上 @EqualsAndHashcode(callSuper=true),覆盖 @Data 中的,设置让其生成的方法中调用父类方法。
    2. 可用 @Getter @Setter @ToString 代替 @Data、并自定义 equals(Object other)hashCode()方法;比如有些类只需要判断主键id是否相等即可。
  3. 另外,Java 规定,重写 equals() 必须重写 hashCode() 方法;
  4. @EqualsAndHashCode.Include @EqualsAndHashCode(onlyExplicitlyIncluded = true)来精确指定要使用的字段或方法。
1
2
3
4
5
6
@Schema(description = "用户 APP - 用户收件地址更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AppAddressUpdateReqVO extends AppAddressBaseVO {
}

@Getter/@Setter、@ToString

  1. @Getter / @Setter:用在类/属性上
    1. 若字段的类型为boolean,则为isXxx()
    2. 用在类上时,默认 @Setter(AccessLevel.PUBLIC) ,指定访问级别有PUBLIC, PROTECTED, PACKAGE, PRIVATE, NONE
    3. @Getter(lazy=true):可替代经典的 Double Check Lock 样板代码。
  2. @ToString:用在类上,自动覆写 toString() 方法,默认打印所有非静态字段;更常用 @Override 重写 toString() 方法的方式实现;
  3. 类上用 @ToString(exclude=”id”)、字段上用 @ToString.Exclude:用于排除 id 属性;
    1. @ToString(onlyExplicitlyIncluded = true),配合用 @ToString.Include 在字段上标记要包含的每个字段;
    2. @ToString(callSuper=true, includeFieldNames=true):调用父类的 toString()方法,将父类实现的输出包含到当前输出中,包含所有属性。

@NonNull

  • @NonNull:添加在方法参数类属性上,用于自动生成 null 参数检查。若确实是 null 时,抛出 NullPointerException 异常。

@Slf4j 示例

构造器

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor:用在类上,为类自动生成对应参数的构造方法。

  1. 指定 staticName = "of"参数:生成返回类对象的静态工厂方法;
  2. @NoArgsConstructor:生成无参构造函数。
    1. 如果字段由 final 修饰,则将导致编译器错误,除非使用@NoArgsConstructor(force = true),否则所有 final 字段都将初始化为 0 / false / null
    2. 对于具有约束(如@NonNull)的字段,不会生成任何检查。
  3. @RequiredArgsConstructor:为每个需特殊处理的字段生成有1个参数的构造函数。
    1. 所有未初始化的 final 字段;
    2. 所有未声明其位置的、未标记为@NonNull的字段。
  4. @AllArgsConstructor:为每个字段生成有1个参数的构造函数。标有@NonNull的字段将对这些参数进行非空检查。

@Builder

见设计模式文档。

@Builder:只能标注到类上,给该类加个构造者模式 Builder 内部类。

  • 生成类当前流程的一种链式(流式)构造工厂,多用于配置类。如:
1
User buildUser = User.builder().username("riemann").password("123").build();

还可以方便的实现建造者模式

@Builder 注解:为类生成相对略微复杂的构建器 API

  • 作用于类,将其变成建造者模式
  • 会生成一个全参的构造函数
  • 可以以链的形式调用
  • 初始化实例对象生成的对象是不可以变的,可以在创建对象的时候进行赋值
  • 如果需要在原来的基础上修改可以加 set 方法,final 字段可以不需要初始化

@Builder 内部做了什么

  • 创建一个名为 ThisClassBuilder 的内部静态类,并具有和实体类相同的属性(称为构建器)
  • 在构建器中:对于目标类中的所有的属性和未初始化的 final 字段,都会在构建器中创建对应属性
  • 在构建器中:创建一个无参的 default 构造函数
  • 在构建器中:实体类中的每个参数,都会对应创建类似于 setter 的方法,方法名与该参数名相同。 并且返回值是构建器本身(便于链式调用)
  • 在构建器中:会创建一个 build 方法,调用 build 方法,就会根据设置的值进行创建实体对象
  • 在构建器中:会生成一个 toString 方法
  • 在实体类中:会创建一个 builder 方法,目的是用来创建构建器

属性介绍

  • @Builder.Default:非 final 的字段可以有默认值

  • builderMethodName:指定创建内部静态类的方法名,默认值为 builder

  • buildMethodName:指定创建实体类的方法名,默认值为 build

  • builderClassName:指定内部静态的类名,默认值为 “”,默认创建的类名为 thisclassBuilder

  • toBuilder:设置为 true 可以对这个对象进行拷贝生成新的对象,可以再修改,默认为 false

  • access:设置 builderMethodName 的访问权限修饰符,默认为 public。
    • 共有 PUBLIC、MODULE、PROTECTED、PACKAGE、PRIVATE,其中 MODULE 是 Java 9 的新特性
  • setterPrefix:设置 setter 方法的前缀,默认为 “”

用法

只需要定义一个静态公共的内部类即可。

如果项目中有使用lombok的话,可以直接使用@Builder注解来实现。

1
2
3
4
5
6
7
8
9
10
11
import lombok.Builder;
import lombok.ToString;

@ToString
@Builder
@AllArgsConstructor
public class UserExample {
    private Integer id;
    private String name;
    private String address;
}

如何使用:

1
2
3
4
5
6
7
UserExample userExample = UserExample.builder()
                .id(1)
                .name("aaa")
                .address("bbb")
                .build();

System.out.println(userExample);

没有父类的属性

问题:子类的Builder对象没有父类的属性

  1. 对于父类,使用@AllArgsConstructor注解。
  2. 对于子类,手动编写全参数构造器,内部调用父类全参数构造器,在子类全参数构造器上使用@Builder注解

通过这种方式,子类Builder对象可以使用父类的所有私有属性

但也有两个副作用:

  • 因为使用@AllArgsConstructor注解,父类构造函数字段的顺序由声明字段的顺序决定,如果子类构造函数传参的时候顺序不一致,字段类型还一样的话,出了错不好发现。
  • 如果父类字段有增减,所有子类的构造器都要修改。

日志注解

见整合 ELK 统一日志框架文档

@CommonsLog@Flogger@Log@JBossLog@Log4j@Log4j2@Slf4j@Slf4jX 注解,添加在上,自动为类添加对应的日志支持。

其它注解

  1. @Cleanup:添加在方法中的局部变量上,在作用域结束时会自动调用 #close() 方法,来释放资源。例如说,使用在 Java IO 流操作的时候。

    • 自动管理资源,用在局部变量前,在当前变量范围内即将执行完毕退出前自动清理资源,生成 try-finally 这样的代码来关闭流;用于确保已分配的资源被释放(IO的连接关闭)。
  2. @SneakyThrows:添加在方法上,给该方法添加 try catch 代码块。自动抛出受检异常,而无需显式在方法上使用 throws 语句;
  3. @Synchronized:用在方法上,添加同步锁。

    • 将方法声明为同步的,并自动加锁;自动添加到同步机制,生成的代码并不是直接锁方法,而是锁代码块。

    • 而锁对象是一个私有的属性$lock$LOCK,而 synchronized 关键字锁对象是this,锁在this或自己的类对象上存在副作用,不能阻止非受控代码去锁this或类对象,这可能会导致竞争条件或其它线程错误;

  4. @Accessors:添加在方法属性上,并设置 chain = true,实现链式编程。用于配置lombok如何生成和查找gettersetter
  5. gettersetterbean命名规范;
    1. 参数 chain=true时,类的所有属性的setter方法返回值为this,支持链式写法。
0%