Java工具—Lombok
Lombok的介绍和安装
参考:
- 官网:https://projectlombok.org/
- Lombok介绍:
- Lombok是一个优秀的Java代码库,简化了Java的编码,为Java代码的精简提供了一种方式
- 每个JavaBean都会写getter、setter、equals、hashCode、toString等代码,Lombok消除Java的冗长代码,尤其是对于简单的Java对象,只要加上注解就行
Lombok的使用
- pom.xml 添加依赖进行版本管理
<properties>
<!--锁定 jdk 版本为 1.8-->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.16-->
<!--scope=provided,说明它只在编译阶段生效,不需要打入包中, Lombok在编译期将带Lombok注解的Java文件正确编译为完整的Class文件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
</dependencies>
-
添加IDE工具对Lombok的支持:点击File-- 》Settings界面,安装Lombok插件,然后重启idea
-
IDEA里需要在设置中启用annotation processors,记得重启IDEA!
Lombok常用注解介绍
1、@Data:在JavaBean中使用,注解包含包含getter、setter、NoArgsConstructor注解
2、@Value注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法
3、@getter和@setter:在JavaBean中使用,注解会生成对应的getter和setter方法
4、@NoArgsConstructor:在JavaBean中使用,注解会生成对应的无参构造方法
@AllArgsConstructor:在JavaBean中使用,注解会生成对应的有参构造方法
@RequiredArgsConstructor :生成private构造方法,使用staticName选项生成指定名称的static方法。
5、@ToString:在JavaBean中使用,注解会自动重写对应的toStirng方法
@ToString(exclude={"column1","column2"}):排除多个column列所对应的元素
@ToString(of={"column1","column2"}):只生成包含多个column列所对应的元素
6、@EqualsAndHashCode:在JavaBean中使用,注解会自动重写对应的equals方法和hashCode方法
7、@Slf4j和@Log4j:在需要打印日志的类中使用,项目中使用slf4j和log4j日志框架
8、@NonNull:注解快速判断是否为空,为空抛出java.lang.NullPointerException
9、@Accessors(chain = true):链式风格,在调用set方法时,返回这个类的实例对象
10、@Synchronized:注解自动添加到同步机制,生成的代码并不是直接锁方法,而是锁代码块, 作用范围是方法上
11、@Cleanup:注解用于确保已分配的资源被释放(IO的连接关闭)
常用注解—@Setter/@Getter
注解详解:
- 作用类上,生成所有成员变量的getter/setter方法
- 作用于成员变量上,生成该成员变量的getter/setter方法
- 方法控制访问级别 set和get注解加:@Setter/@Getter(AccessLevel.PROTECTED)
- 忽略不生成某个字段set/get方法:@Setter/@Getter(AccessLevel.NONE)
- final成员变量只会生成get
- static静态成员变量不会生成set/get方法
新建实体类:User.java
@Setter
@Getter
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
public User() {
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
更多属性示例:
新建实体类:User.java
@Setter
@Getter
public class User {
private Long id;
/**
* 不想生成 get方法
*/
@Getter(AccessLevel.NONE)
private Integer age;
/**
* 控制访问权限
*/
@Getter(AccessLevel.PROTECTED)
private int salary;
/**
* final 只会生成get
*/
private final String name="Sam";
/**
* 下面两个静态成员变量不会生成set/get方法
*/
static Date createTime = new Date();
private static final String address = "广东省广州市";
}
编译查看字节码:mvn compile
public class User {
private Long id;
private Integer age;
private int salary;
private final String name = "Sam";
static Date createTime = new Date();
private static final String address = "广东省广州市";
public User() {
}
public void setId(Long id) {
this.id = id;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSalary(int salary) {
this.salary = salary;
}
public Long getId() {
return this.id;
}
public String getName() {
this.getClass();
return "Sam";
}
protected int getSalary() {
return this.salary;
}
}
常用注解—@XxArgsConstructor
@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor
@NoArgsConstructor:生成对应的无参构造方法(一般情况下不会单独使用该注解,因为默认就有无参构造)
@NoArgsConstructor
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
public User() {
}
}
@AllArgsConstructor:生成对应的有参构造方法(全部参数)
@AllArgsConstructor
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
}
注意:一般@AllArgsConstructor和@NoArgsConstructor一起使用,因为使用@AllArgsConstructor后默认就不会生成无参构造了
@RequiredArgsConstructor:生成一个包含 "特定参数" 的构造器,特定参数指的是:
- 加上 final 修饰词的变量们
- 加上@NonNull注解的变量们
- 注意:不能加上@NoArgsConstructor
@RequiredArgsConstructor
public class User {
@NonNull
private Long id;
private final String name;
}
编译查看字节码:mvn compile
public class User {
@NonNull
private Long id;
private final String name;
public User(@NonNull Long id, String name) {
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
} else {
this.id = id;
this.name = name;
}
}
}
常用注解—@NonNull
@NonNull:Lombok非空判断、作用于方法上或者属性,用于非空判断,如果为空则抛异常
@Setter
@Getter
public class User {
@NonNull
private Long id;
private String name;
}
编译查看字节码:mvn compile
import lombok.NonNull;
public class User {
@NonNull
private Long id;
private String name;
public User() {
}
public void setId(@NonNull Long id) {
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
} else {
this.id = id;
}
}
public void setName(String name) {
this.name = name;
}
@NonNull
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
常用注解—@ToString
@ToString:注解会自动重写对应的toStirng方法
@ToString(exclude={"column1","column2"}):排除多个column列所对应的元素
@ToString(of={"column1","column2"}):只生成包含多个column列所对应的元素
@ToString
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
public User() {
}
public String toString() {
return "User(id=" + this.id + ", name=" + this.name + ")";
}
}
常用注解—@EqualsAndHashCode
@EqualsAndHashCode:作用于类,覆盖默认的equals和hashCode, 作用于全部属性
@EqualsAndHashCode(exclude = {"age"}):不包括某个属性
@EqualsAndHashCode(of = {"name"}):只输出某个属性
@EqualsAndHashCode
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
public User() {
}
public 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 {
Object this$id = this.id;
Object other$id = other.id;
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this$id.equals(other$id)) {
return false;
}
Object this$name = this.name;
Object other$name = other.name;
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof User;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.id;
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $name = this.name;
result = result * 59 + ($name == null ? 43 : $name.hashCode());
return result;
}
}
常用注解—@Data/@Value
Lombok太多常用的注解一个个加也麻烦,所以出现了两个聚合注解:@Data 和 @Value
@Data:整合包,只要加了 @Data 这个注解,等于同时加了以下注解
- @Getter、@Setter
- @ToString
- @EqualsAndHashCode
- @RequiredArgsConstructor
元注解:
/**
* @see Getter
* @see Setter
* @see RequiredArgsConstructor
* @see ToString
* @see EqualsAndHashCode
* @see lombok.Value
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
String staticConstructor() default "";
}
使用案例:
@Data
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
// @RequiredArgsConstructor
public User() {
}
// @Getter、@Setter
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
// @EqualsAndHashCode
public 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 {
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this$id.equals(other$id)) {
return false;
}
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;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof User;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
return result;
}
// @ToString
public String toString() {
return "User(id=" + this.getId() + ", name=" + this.getName() + ")";
}
}
@Data是使用频率最高的lombok注解,通常@Data 会在一个值可以被更新的对象上,像是日常使用的 DTO 们、或是 JPA 里的 Entity 们,就很适合加上 @Data 注解,也就是:@Data for mutable class
@Value:也是整合包,但是他会把所有的变量都设成 final 的,其他的就跟 @Data 一样,等于同时加了以下注解:
- @Getter (注意没有@Setter)
- @ToString
- @EqualsAndHashCode
- @RequiredArgsConstructor
元注解:
/**
* @see lombok.Getter
* @see lombok.experimental.FieldDefaults
* @see lombok.AllArgsConstructor
* @see lombok.ToString
* @see lombok.EqualsAndHashCode
* @see lombok.Data
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Value {
String staticConstructor() default "";
}
使用案例:
@Value
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public final class User {
// 给属性都设置成final
private final Long id;
private final String name;
// @RequiredArgsConstructor
public User(Long id, String name) {
this.id = id;
this.name = name;
}
// @Getter
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
// @EqualsAndHashCode
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof User)) {
return false;
} else {
User other = (User)o;
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this$id.equals(other$id)) {
return false;
}
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;
}
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
return result;
}
// @ToString
public String toString() {
return "User(id=" + this.getId() + ", name=" + this.getName() + ")";
}
}
上面那个 @Data 适合用在 POJO 或 DTO 上,而这个 @Value 注解,则是适合加在值不希望被改变的类上,像是某个类的值当创建后就不希望被更改,只希望我们读它而已,就适合加上 @Value 注解,也就是 @Value for immutable class
另外注意一下,此 lombok 的注解 @Value 和另一个 Spring 的注解 @Value 撞名,在 import 时不要 import 错了
建造模式—@Builder
@Builder注解:作用在类上,自动生成流式 set 值写法,从此之后再也不用写一堆 setter 了
@Builder
public class User {
private Long id;
private String name;
}
// 使用
User build = User.builder().id(1L).name("Sam").build();
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
User(Long id, String name) {
this.id = id;
this.name = name;
}
public static User.UserBuilder builder() {
return new User.UserBuilder();
}
public static class UserBuilder {
private Long id;
private String name;
UserBuilder() {
}
public User.UserBuilder id(Long id) {
this.id = id;
return this;
}
public User.UserBuilder name(String name) {
this.name = name;
return this;
}
public User build() {
return new User(this.id, this.name);
}
public String toString() {
return "User.UserBuilder(id=" + this.id + ", name=" + this.name + ")";
}
}
}
注意:虽然只要加上@Builder注解,我们就能够用流式写法快速设定对象的值,但是 setter 还是必须要写不能省略的,因为 Spring 或是其他框架有很多地方都会用到对象的 getter/setter 对他们取值/赋值
所以通常是@Data和@Builder 会一起用在同个类上,既方便我们流式写代码,也方便框架做事
常用注解—@Accessors
@Accessors:中文含义是存取器,@Accessors用于配置getter和setter方法的生成结果,下面介绍三个属性
fluent:中文含义是流畅的,设置为true,则getter和setter方法的方法名都是基础属性名,且setter方法返回当前对象
@Data
@Accessors(fluent = true)
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
// 生成的getter和setter方法如下,方法体和其他方法略
public Long id() {}
public User id(Long id) {}
public String name() {}
public User name(String name) {}
}
chain:中文含义是链式的,设置为true,则setter方法返回当前对象。如下
@Data
@Accessors(chain = true)
public class User {
private Long id;
private String name;
}
编译查看字节码:mvn compile
public class User {
private Long id;
private String name;
// 生成的setter方法如下,方法体和其他方法略
public User setId(Long id) {}
public User setName(String name) {}
}
prefix:中文含义是前缀,用于生成getter和setter方法的字段名会忽视指定前缀(遵守驼峰命名)
@Data
@Accessors(prefix = "t")
public class User {
private Long tId;
private String tName;
}
@Data
@Accessors(prefix = {"p","t"})
public class User {
private Long tId;
private String pName;
}
编译查看字节码(如上两种都编译后都会生成如下字节码):mvn compile
public class User {
private Long pId;
private String pName;
// 生成的getter和setter方法如下,方法体略
public Long getId() {}
public void setId(Long id) {}
public String getName() {}
public void setName(String name) {}
}
日志注解—@Slf4j/@Log4j
@Slf4j:自动生成该类的log静态常量,直接使用,不用再手动 new log 静态常量了
除了 @Slf4j 之外,lombok也提供其他日志框架的变种注解可以用,@Log4j、@Log...他们都是帮我们创建一个静态常量 log,只是使用的库不一样而已
@Slf4j
public class User {
// 编译查看字节码:mvn compile
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(User.class);
public static void main(String[] args) {
log.info("lombok-->Slf4j");
}
}
其他日志框架:
@Slf4j //对应的log语句如下
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(User.class);
@Log4j //对应的log语句如下
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log //对应的log语句如下
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
注意:必须引入对应的日志框架依赖
SpringBoot默认支持的就是 slf4j + logback 的日志框架,所以也不用再多做啥设定,直接就可以用在 SpringBoot project上,log 系列注解最常用的就是 @Slf4j
Lombok插件原理-对比反射技术
讲解JSR介绍和lombok原理讲解
- 熟悉Java自定义注解的同学已经猜到是: JSR 269插件化注解处理
JSR 269: Pluggable Annotation Processing API
实现在Javac编译阶段利用“Annotation Processor”对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件
地址:https://www.jcp.org/en/jsr/detail?id=269
- 科普
JSR是Java Specification Requests的缩写,意思是Java 规范提案。
是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。
任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
- Lombok解析流程
Javac 解析成AST抽象语法树后, Lombok根据自己编写的注解处理器,动态地修改 AST增加新的节点(即Lombok自定义注解所需要生成的代码),最终生成JVM可执行的字节码Class文件
可以看编译后的在target目录下的class文件
- 能实现上述效果的还有一个反射技术,那两个对比如何?
使用Annotation Processing自定义注解是在编译阶段进行修改
JDK的反射技术是在运行时动态修改
结论:反射更加灵活一些但是带来的性能损耗更加大