Lombok快速入门

Lombok介绍:

  • Lombok其实就是取名自龙目岛(Pulau Lombok),龙目岛是印度尼西亚西努沙登加拉(Nusa Tenggara Barat)省岛屿,西隔龙目海峡面对巴厘岛,东隔阿拉斯(Alas)海峡面松巴哇(Sumbawa)岛,北濒爪哇海,南临印度洋。
  • 在编程上,Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。简而言之,一句话就是:通过简单的注解来精简代码达到消除冗长代码的目的。
  • Lombok官网
  • github地址

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;
}

然后可以看到这个类自动生成了这些方法:


image.png

如果觉得@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原理:

  • Lombok 实现了 JSR 269 Pluggable Annotation Processing API 规范,也就是可插拔注释处理
  • javac 从 JDK6 开始支持 “JSR 269 API” 规范
  • 只要程序实现了该API,就能在javac运行的时候得到调用
  • 而Lombok实现了 “JSR 269 API” 规范 ,在编译时,javac编译源码的具体流程如下:


    image.png

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 :


image.png

搜索Lombok Plugin进行安装:


image.png

安装完成后,重启IDEA:


image.png

Eclipse安装Lombok插件:

1.安装该插件时最好关闭Eclipse,然后在官网中下载lombok.jar,下载地址

2.将 lombok.jar 放在eclipse安装目录下,和 eclipse.ini 文件平级的。


image.png

3.双击运行 lombok.jar

如果没法直接双击运行的话,就在 lombok.jar 的目录下,打开cmd命令行,运行如下命令:

java -jar lombok.jar

如果以下提示的权限问题则使用管理员身份运行即可:


image.png

注:Mac/Linux 系统下则使用 sudo java -jar lombok.jar 命令进行运行即可,但是要确保执行用户有sudo权限。

成功运行后会弹框如下框,一开始可能会加载些东西,加载完成后界面如下:


image.png

安装成功后如下图:


image.png

打开Eclipse,看看是否已安装Lombok插件,如下则是安装成功:


image.png

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反编译工具:

  • Java Decompiler
  • JD 官网地址
  • 分为以下几类
    • JD-GUI,独立的图形化软件
    • JD-Eclipse,可以集成到Eclipse插件
    • JD-Intellij,可以集成到IDEA插件

这里提到反编译工具的原因是因为Lombok是编译时修改的抽象语法树,所以我们想查看编译后的.class文件的源码就需要使用反编译工具。这里所介绍到的 Java Decompiler 就是用来帮助我们在使用Lombok遇到问题时,去验证编译后的.class文件的。


使用Lombok时需要注意的点

  • 在类需要序列化、反序列化时或者需要详细控制字段时,应该谨慎考虑是否要使用Lombok,因为在这种情况下容易出问题。例如:Jackson、Json 序列化
  • 使用Lombok虽然能够省去手动创建setter和getter方法等繁琐事情,但是却降低了源代码文件的可读性和完整性,减低了阅读源代码的舒适度
  • 使用@Slf4j还是@Log4j注解,需要根据实际项目中使用的日志框架来选择。
  • Lombok并非处处适用,我们需要选择适合的地方使用Lombok,例如pojo是一个好地方,因为pojo很单纯

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对象的声明代码,这样我们就不需要总是每个类都去写这句代码了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容