Lombok使用指南(上)
1、Lombok简介
根据官网(https://projectlombok.org)的介绍:Project Lombok 是一个 Java 库,它能自动与您的编辑器和构建工具集成,为您的 Java 代码增添活力。从此再也不用编写 getter 或 equals 方法,只需一个注解,您的类就能拥有功能完备的构建器,自动处理日志变量等等。
简而言之,Lombok 能以简单的注解形式来简化java代码,提高开发人员的开发效率。特别是开发中有大量的实体类,VO,DTO等。
不过在实际开发中,Lombok的使用也存在一定的争议。可以说其优缺点并存。
优点包括:
-
通过注解的形式自动
-
生成构造器、getter/setter、equals、hashCode、toString等方法,提高了一定的开发效率
-
让代码变得简洁,不用过多的去关注相应的方法
-
属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
缺点包括:
-
虽然省去了手动创建getter/setter等方法的麻烦,但降低了源代码的可读性和完整性,降低了阅读源代码的舒适度
-
不支持多种参数构造器的重载
我将Lombok提供的注解大致为分为一下几类:
- 通用方法产生:比如 setter与getter方法,Object类中的toString,eauals,hashCode方法
- 对象创建:构造方法,构建器模式创建对象,withXxx,ofXxx模式,资源清理等
- 异常处理:自动产生try-catch结构,自动在finally子句中释放资源
- 线程:在方法体内加锁
- 日志:自动在类中增加不同日志记录的对象
- 其他
2、搭建测试环境
本文的测试环境为Windows 11,IntelliJ IDEA 2025.2.2 (Community Edition),JDK 25,Maven 3.9.6,Lombok 1.18.42。
IDEA自2023版本就默认支持,开发中引入依赖即可,如果是早期版本,需要单独安装Lombok插件。
在IDEA创建Maven项目,增加Lombok依赖:
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.42</version>
</dependency>
本文下面针对每个注解及重要属性进行介绍。
Lombok本身是操作class文件,既不会修改源文件,也不会产生新的源文件。
本文针对每个注解产生的源码,是由编译之后的 class 反编译而成,仅供学习时理解其基本原理。
3、通用方法相关注解
3.1、Setter注解
Setter注解用于修饰类或其属性。
- 修饰类,为类中的非 final,非 static 且未使用 @Setter 的属性产生 setter 方法
- 修饰非 final 非 static 属性,为该属性产生对应的 setter 方法
Setter注解的属性:
- value:用于指定访问控制修饰符,类型是 lombok.AccessLevel 枚举,其可选值有:PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, NONE;默认是.PUBLIC。
AccessLevel 枚举在其他注解中也有使用,使用时可以类比,其源码如下,:
public enum AccessLevel {PUBLIC, // 产生pblic修饰的setter方法@Deprecated MODULE, // 弃用,与PACKAGE效果一样PROTECTED, // 产生protected修饰的setter方法PACKAGE, // 产生包内私有的setter方法(没有关键字修饰)PRIVATE, // 产生private修饰的setter方法NONE; // 不产生setter方法
}
用于修饰类的示例:
@Setter
public class User {private int id;private String name;private final int num = 0;public static String desc;
}
通过反编译得到的源码:
public class User {private int id;private String name;private final int num = 0;public static String desc;@Generatedpublic void setId(int id) {this.id = id;}@Generatedpublic void setName(String name) {this.name = name;}
}
可以看到 Lombok 帮我们产生了两个setter方法,都是针对非 final,非 static属性的。
@Generated是Lombok自动为它产生的元素加上的注解,可以不用关心。
当然也可以在属性上使用@Setter注解,那么只会对其修饰的属性产生setter方法,优先级比类上的注解高。
- onMethod:为Setter方法增加指定的注解
- onParam:为Setter方法的参数增加指定的注解
onMethod 与 onParam 使用示例如下,注意使用时属性结尾有下划线:
@Setter
public class User {@Setter(onMethod_ = @MyAnnotation)private int id;@Setter(onMethod_ = {@MyAnnotation, @Deprecated})private String name;private int num;@Setter(onParam_ = @MyAnnotation)public String desc;
}
产生的源码如下:
public class User {private int id;private String name;private int num;public String desc;@Generatedpublic void setNum(int num) {this.num = num;}@MyAnnotation@Generatedpublic void setId(int id) {this.id = id;}/** @deprecated */@Deprecated@MyAnnotation@Generatedpublic void setName(String name) {this.name = name;}@Generatedpublic void setDesc(@MyAnnotation String desc) {this.desc = desc;}
}
可以看到每一个方法或参数都按照@Setter 中指定的信息产生了指定的代码。
上面使用的注解 @Deprecated 是JDK自带的,而 @MyAnnotation 是自已定义的:
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface MyAnnotation {
}
3.2、Getter注解
Getter 注解用于修饰类或其属性。用法与Setter很类似,不同点在于该注解可以为 final 属性产生getter方法。
-
修饰类,为类中的非 static 且未使用 @Getter 的属性产生 getter 方法
-
修饰非 static 属性,为该属性产生对应的 getter 方法
Getter注解的属性:
-
value:与Setter的属性value相同,用于指定产生getter方法的访问控制修饰符,就不赘述了
-
onMethod:与Setter的属性onMethod相同,用于在产生的getter方法上产生指定的注解
-
lazy:用于修饰private final 成员的注解上,产生一个线程安全的取值方法
使用示例:
@Getter
public class User {private int id;private String name;private final int num = 0;
}
没有特别指定属性 lazy,默认就是false,产生的源码如下:
public class User {private int id;private String name;private final int num = 0;@Generatedpublic int getId() {return this.id;}@Generatedpublic String getName() {return this.name;}@Generatedpublic int getNum() {Objects.requireNonNull(this);return 0;}
}
对于fnal属性,仅仅做了一个非空校验。如果设置 lazy 为 true:
@Getter
public class User {private int id;private String name;@Getter(lazy = true)private final int num = 0;
}
产生的源码如下:
import java.util.concurrent.atomic.AtomicReference;
import lombok.Generated;public class User {private int id;private String name;private final AtomicReference<Object> num = new AtomicReference();@Generatedpublic int getId() {return this.id;}@Generatedpublic String getName() {return this.name;}@Generatedpublic int getNum() {// 通过this.num.get()获取当前值并存储在$value变量中Object $value = this.num.get(); // 第一次检查$value是否为null(双重检查锁的第一次检查)if ($value == null) {//如果为null,进入同步块(使用this.num作为锁对象)synchronized(this.num) {$value = this.num.get();// 在同步块内再次检查$value是否为null(双重检查锁的第二次检查)if ($value == null) {//如果仍然为null,则初始化值为 0,将其赋值给$value并通过this.num.set($value)设置到原子引用中int actualValue = 0;$value = 0;this.num.set($value);}}}// 最后将$value强制转换为Integer并返回return (Integer)$value;}
}
这段代码中的getNum()
方法实现了一个线程安全的 “懒加载” 初始化逻辑,用于获取num
变量的值。这是 “双重检查锁定”(Double-Checked Locking) 设计模式的实现,目的是在多线程环境下保证num
只会被初始化一次,既保证了线程安全,又避免了每次获取值时都进入同步块带来的性能损耗。这种实现方式在需要延迟初始化且有线程安全要求的场景中比较常见。
3.3、ToString注解
ToString注解只能修饰类,用于为类产生 toString 方法。不指定该注解的任何属性,会将类中的所有属性直接拼接到返回的字符串结果中。如果属性有对应的getter方法,则使用getter方法获取值,没有对应的getter方法,直接使用属性值。如下面类中的属性是没有对应getter方法的:
@ToString
public class User {private int id;private String name;private Address address; // 另一个类
}
最终产生的toString如下,获取属性的值直接就是属性:
public String toString() {return "User(id=" + this.id + ", name=" + this.name + ", address=" + this.address + ")";
}
如果在类上使用了Getter注解,产生的toString方法如下:
public String toString() {return "User(id=" + this.getId() + ", name=" + this.getName() + ", address=" + this.getAddress() + ")";
}
ToString注解的属性:
- includeFieldNames:返回的字符串信息中是否使用包含成员属性名,默认是 true,如果设置为false,产生的toString方法如下:
public String toString() {return "User(" + this.getId() + ", " + this.getName() + ", " + this.getAddress() + ")";
}
- exclude:排除不出现在返回字符串结果中的成员属性名
@ToString(exclude = {"id","address"}) // 排除属性 id 与 address
public class User {private int id;private String name;private Address address;
}
结果为:
public String toString() {return "User(name=" + this.getName() + ")";
}
不过该属性很快会被弃用,官方推荐使用@ToString.Exclude :
@Getter
@ToString
public class User {@ToString.Excludeprivate int id;private String name;@ToString.Excludeprivate Address address;
}
- callSuper:设置是否包含父类的toString实现,默认是false;设置为true,则产生的toStirng为:
public String toString() {return "User(super=" + super.toString() + ", id=" + this.getId() + ", name=" + this.getName() + ", address=" + this.getAddress() + ")";
}
- doNotUseGetters:设置是否不使用 getter 方法获取属性的值,默认是 false,如果设置为 true,则toStirng方法为:
public String toString() {return "User(id=" + this.id + ", name=" + this.name + ", address=" + this.address + ")";
}
- of:指定toString返回的字符串中要包含的属性
@Getter
@ToString(of={"name"})
public class User {private int id;private String name;private Address address;
}
产生的toString方法为:
public String toString() {return "User(name=" + this.getName() + ")";
}
返回的字符串只包含of属性中指定的name属性。该属性也很快会被弃用,官方推荐使用@ToString.Include注解,与onlyExplicitlyIncluded=true一起使用。
- onlyExplicitlyIncluded:设置是否只包含使用了ToString.Include修饰的属性,默认为false;如果设置为true,如下所示:
@Getter
@ToString(onlyExplicitlyIncluded = true)
public class User {private int id;@ToString.Includeprivate String name;private Address address;
}
产生的toString为:
public String toString() {return "User(name=" + this.getName() + ")";
}
可以看到toString返回的字符串中只包含了name属性的值。
3.4、EqualsAndHashCode注解
EqualsAndHashCode修饰类,用于产生equals方法和hashCode方法。例如:
@Getter
@EqualsAndHashCode
public class User {private int id;private String name;
}
产生的源码如下:
public class User {private int id;private String name;@Generatedpublic int getId() {return this.id;}@Generatedpublic String getName() {return this.name;}@Generatedpublic boolean equals(Object o) {if (o == this) {return true;} else if (!(o instanceof User)) {return false;} else {User other = (User)o;if (!other.canEqual(this)) {return false;} else if (this.getId() != other.getId()) {return false;} else {Object this$name = this.getName();Object other$name = other.getName();if (this$name == null) {if (other$name != null) {return false;}} else if (!this$name.equals(other$name)) {return false;}return true;}}}@Generatedprotected boolean canEqual(Object other) {return other instanceof User;}@Generatedpublic int hashCode() {int PRIME = 59;int result = 1;result = result * 59 + this.getId();Object $name = this.getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());return result;}
}
EqualsAndHashCode注解的属性:
-
exclude:指定排除参与equals方法与hashCode方法的属性,很快被弃用,官方推荐使用@EqualsAndHashCode.Exclude,与 @ToString 的 exclude 属性类似,就不再演示了
-
of:指定参与equals方法与hashCode方法的属性,很快被弃用,官方推荐使用@EqualsAndHashCode…Include,与 onlyExplicitlyIncluded=true一起使用,就不再演示了
-
onlyExplicitlyIncluded:设置是否值显式包含 @EqualsAndHashCode.Include 修饰的属性
-
callSuper:产生equals与hashCode方法时是否调用父类的对应方法,默认为false;与@ToString中的callSuper属性类似,不再赘述
-
doNotUseGetters:与@ToString中的doNotUseGetters属性类似,就不赘述了。
-
onParam:在产生的equals方法中参数前增加该属性指定的注解,与Setter注解中的onParam类似。
-
cacheStrategy:设置hashCode的缓存策略,CacheStrategy 枚举类型,只有 NEVER 与 LAZY 两个值,默认值是CacheStrategy.NEVER,即不缓存类如㝉设置为true,则 hashCode 方法如下:
private transient int $hashCodeCache;public int hashCode() {if (this.$hashCodeCache != 0) {return this.$hashCodeCache;} else {int PRIME = 59;int result = 1;result = result * 59 + this.id;Object $name = this.name;result = result * 59 + ($name == null ? 43 : $name.hashCode());if (result == 0) {result = Integer.MIN_VALUE;}this.$hashCodeCache = result;return result;}
}
可以看到,在类中增加了一个私有属性,用于缓存产生的 hash 值。