标签:Lombok JSR 269 API 可插拔注释处理 修改抽象语法树 Java
Lombok介绍:Lombok优点:
注:IDE上必须要支持Lombok,否则IDE会报错。
为什么说Lombok可以使代码更简洁、可以消除冗长代码呢?我们来拿lombok官网的一个例子来说:
public class Mountain{
private String name;
private double longitude;
private String country;
}
要使用这个对象,必须还要写一些getter和setter方法,可能还要写一个构造器、equals方法、或者hash方法。这些方法很冗长而且没有技术含量,我们叫它样板式代码。
lombok的主要作用是通过一些注解,消除样板式代码,像这样:
@Data
public class Mountain{
private String name;
private double longitude;
private String country;
}
然后可以看到这个类自动生成了这些方法:
如果觉得@Data这个注解有点简单粗暴的话,Lombok提供一些更精细的注解,比如@Getter、@Setter,(这两个是field注解),@ToString,@AllArgsConstructor(这两个是类注解)。这些可能是最常见的用法,更详细的用法可以参考[Lombok feature]overview(https://projectlombok.org/features/)
Lombok既是一个IDE插件,也是一个项目要依赖的jar包。Lombok是依赖jar包的原因是因为编译时要用它的注解。是插件的原因是他要在编译器编译时通过操作AST(抽象语法树)改变字节码生成。也就是说他可以改变java语法.。他不像spring的依赖注入或者hibernate的orm一样是运行时的特性,而是编译时的特性。
Lombok原理:
1.javac对源代码(Source File)进行分析(Parse),生成一棵抽象语法树(AST)
2.运行过程(Annotation Processing)中调用实现了 "JSR 269 API" 的Lombok程序(Lombok Annotation Processor)
3.此时Lombok就对第一步骤得到的AST进行处理(Lombok Annotation Handler),找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
4.javac使用修改后的抽象语法树(Modified AST)进行分析生成(Analyze and Generate)字节码文件(Byte Code)
添加Lombok到项目中
创建一个Maven项目,通过pom.xml配置Lombok依赖到项目中,配置依赖如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
IDEA安装Lombok插件
然后还需要在IDE中安装Lombok插件,我这里使用的是IDEA,所以先以IDEA为例做演示。点击右上角的 File -> setting -> Plugins :
搜索Lombok Plugin进行安装:
安装完成后,重启IDEA:
Eclipse安装Lombok插件:
1.安装该插件时最好关闭Eclipse,然后在官网中下载lombok.jar,下载地址
2.将 lombok.jar 放在eclipse安装目录下,和 eclipse.ini 文件平级的。
3.双击运行 lombok.jar
如果没法直接双击运行的话,就在 lombok.jar 的目录下,打开cmd命令行,运行如下命令:
java -jar lombok.jar
如果以下提示的权限问题则使用管理员身份运行即可:
注:Mac/Linux 系统下则使用 sudo java -jar lombok.jar
命令进行运行即可,但是要确保执行用户有sudo权限。
成功运行后会弹框如下框,一开始可能会加载些东西,加载完成后界面如下:
安装成功后如下图:
打开Eclipse,看看是否已安装Lombok插件,如下则是安装成功:
Lombok注解
Lombok 常用的注解:
注解 | 描述 |
---|---|
@Getter / @Setter | 可以作用在类上和属性上,放在类上,会对所有的非静态(non-static)属性生成Getter/Setter方法,放在属性上,会对该属性生成Getter/Setter方法。并可以使用该注解中的AccessLevel属性来指定Getter/Setter方法的访问级别。 |
@ToString | 生成toString方法,默认情况下,会输出类名、所有属性,属性会按照顺序输出,以逗号分割。可以使用该注解中的exclude属性来指定生成的toSpring方法不包含对象中的哪些字段,或者使用of属性来指定生成的toSpring方法只包含对象中的哪些字段 |
@EqualsAndHashCode | 默认情况下,会使用所有非瞬态(non-transient)和非静态(non-static)字段来生成equals和hascode方法,也可以使用exclude或of属性。 |
@NoArgsConstructor | 生成无参构造器 |
@RequiredArgsConstructor | 会生成一个包含标识了@NonNull注解的变量的构造方法。生成的构造方法是private,如果想要对外提供使用的话,可以使用staticName选项生成一个static方法。 |
@AllArgsConstructor | 生成全参构造器,当我们需要重载多个构造器的时候,Lombok就无能为力了。 |
@Slf4j | 该注解是用来解决不用每次都写 private final Logger logger = LoggerFactory.getLogger(XXX.class); 这句代码的。使用的日志框架是LogBack |
@Log4j | 该注解也是用来解决不用每次都写日志对象声明语句的,从字面上也可以看出,使用的日志框架是log4j |
@Data | 该注解是 @ToString、@EqualsAndHashCode注解,和所有属性的@Getter注解, 以及所有non-final属性的@Setter注解的组合,通常情况下,我们使用这个注解就足够了。 |
反编译大法
当我们想查看.class文件的源码时,可以使用Java反编译工具:
这里提到反编译工具的原因是因为Lombok是编译时修改的抽象语法树,所以我们想查看编译后的.class文件的源码就需要使用反编译工具。这里所介绍到的 Java Decompiler 就是用来帮助我们在使用Lombok遇到问题时,去验证编译后的.class文件的。
使用Lombok时需要注意的点
我这里拿之前项目中的一个 Category 类来做为演示的例子,在使用Lombok之前,这个类里是写了getter setter方法以及构造函数的。现在我们使用Lombok将代码改造如下:
package org.mmall.pojo;
import lombok.*;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
@ToString(exclude = "updateTime")
public class Category {
private Integer id;
private Integer parentId;
private String name;
private Boolean status;
private Integer sortOrder;
private Date createTime;
private Date updateTime;
}
编译后生成的代码如下,使用反编译工具进行查看:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.mmall.pojo;
import java.beans.ConstructorProperties;
import java.util.Date;
public class Category {
private Integer id;
private Integer parentId;
private String name;
private Boolean status;
private Integer sortOrder;
private Date createTime;
private Date updateTime;
public Integer getId() {
return this.id;
}
public Integer getParentId() {
return this.parentId;
}
public String getName() {
return this.name;
}
public Boolean getStatus() {
return this.status;
}
public Integer getSortOrder() {
return this.sortOrder;
}
public Date getCreateTime() {
return this.createTime;
}
public Date getUpdateTime() {
return this.updateTime;
}
public void setId(Integer id) {
this.id = id;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public void setName(String name) {
this.name = name;
}
public void setStatus(Boolean status) {
this.status = status;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Category() {
}
@ConstructorProperties({"id", "parentId", "name", "status", "sortOrder", "createTime", "updateTime"})
public Category(Integer id, Integer parentId, String name, Boolean status, Integer sortOrder, Date createTime, Date updateTime) {
this.id = id;
this.parentId = parentId;
this.name = name;
this.status = status;
this.sortOrder = sortOrder;
this.createTime = createTime;
this.updateTime = updateTime;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Category)) {
return false;
} else {
Category other = (Category)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;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Category;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
return result;
}
public String toString() {
return "Category(id=" + this.getId() + ", parentId=" + this.getParentId() + ", name=" + this.getName() + ", status=" + this.getStatus() + ", sortOrder=" + this.getSortOrder() + ", createTime=" + this.getCreateTime() + ")";
}
}
如上,从反编译后的代码可以看到,getter setter方法和无参、全参构造器以及equals、hashcode、toString方法都生成出来了。在@EqualsAndHashCode注解中我们使用of属性指定只对比对象中id这个字段,所以生成的equals和hashcode只使用id这个字段作为因子,默认不指定的情况下是使用对象中所有的字段作为因子。而在@ToString注解中,我们使用exclude属性指定updateTime这字段不被输出,所以Lombok生成的toString方法中没有包含updateTime这个字段。
我们再来演示一下@Getter、@Setter以及@RequiredArgsConstructor注解的使用,新建一个测试类,编辑代码如下:
package org.mmall.pojo;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@RequiredArgsConstructor(staticName = "getInstance")
public class Test {
private String name;
@NonNull
private int age;
}
编译后生成的代码如下,使用反编译工具进行查看:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.mmall.pojo;
import java.beans.ConstructorProperties;
import lombok.NonNull;
public class Test {
private String name;
@NonNull
private int age;
public String getName() {
return this.name;
}
@NonNull
public int getAge() {
return this.age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(@NonNull int age) {
this.age = age;
}
@ConstructorProperties({"age"})
private Test(@NonNull int age) {
this.age = age;
}
public static Test getInstance(@NonNull int age) {
return new Test(age);
}
}
可以看到,@RequiredArgsConstructor注解会生成一个包含标识了@NonNull注解的变量的构造方法,并且生成的构造方法是private的,使用staticName选项可以生成一个可以得到该对象实例的static方法。
接下来演示一下@Slf4j注解的使用,因为我项目中使用的是logback,所以使用@Slf4j注解,如果使用的是log4j,则使用@Log4j注解,两者的使用方式是一样的。代码如下:
...
@Service("iCategoryService")
@Slf4j
public class CategoryServiceImpl implements ICategoryService {
public ServerResponse<List<Category>> getChildrenParallelCategory(Integer categoryId) {
List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
if (CollectionUtils.isEmpty(categoryList)) {
log.info("未找到当前分类的子分类");
}
return ServerResponse.createBySuccess(categoryList);
}
}
编译后生成的代码如下,使用反编译工具进行查看:
...
@Service("iCategoryService")
public class CategoryServiceImpl implements ICategoryService {
private static final Logger log = LoggerFactory.getLogger(CategoryServiceImpl.class);
public ServerResponse<List<Category>> getChildrenParallelCategory(Integer categoryId) {
List<Category> categoryList = this.categoryMapper.selectCategoryChildrenByParentId(categoryId);
if (CollectionUtils.isEmpty(categoryList)) {
log.info("未找到当前分类的子分类");
}
return ServerResponse.createBySuccess(categoryList);
}
}
可以看到,Lombok会自动帮我们生成log对象的声明代码,这样我们就不需要总是每个类都去写这句代码了。
标签:Lombok JSR 269 API 可插拔注释处理 修改抽象语法树 Java
原文地址:http://blog.51cto.com/zero01/2112466