Lombok实际开发中的使用和理解工作原理

    在JAVA应用程序中存在许多重复相似的、生成之后几乎不对其做更改的代码,但是我们还不得不花费很多精力编写它们来满足JAVA的编译需求。
    
    比如在JAVA应用程序的开发中,我们几乎要为所有Bean的成员变量添加get(()、set()方法,这些相对固定但又不得不写的代码实在浪费精力,同时让类看起来更加的杂乱。我们希望将有限的精力更多的关注更重要的地方。
 
Lombok诞生很久了,甚至在SpringBootInitlizr中都已经加入了Lombok选项:
Lombok实际开发中的使用和理解工作原理
    虽然大家一直在用Lombok,但是对于Lombok的所有功能是不是全部都掌握呢?
 
 
  • Lombok
 
官网的介绍
    Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again. Early access to future java features such as val, and much more.
 
 
直白的说: 
    Lombok 是一种 Java™ 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的,且看:
 
 
Lombok的安装:
 
运行原理:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
 
    可以看到,以Data注解为例,其应用于编译阶段的【.java->.class】,所以一般IDEA无法自动识别。所以这就是为什么在IDEA中我们使用Lombok的时候需要下载Lombok插件了。
 
对比:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
 
    RestController是在运行期通过反射来实现业务逻辑。
 
安装:
1、添加依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>x.x.x</version>
    <scope>provided</scope>
</dependency>
2、添加插件(推荐)
Lombok实际开发中的使用和理解工作原理
 
 
  • 应用
 
常用注解:
  1. @Getter/@Setter
  2. @ToString
  3. @EqualsAndHashCode
  4. @NonNull
  5. @NoArgsConstructor
  6. @RequiredArgsConstructor
  7. @AllArgsConstructor
  8. @Data
  9. @Builder
  10. @Log
  11. val
  12. @Cleanup
 
 
举例说明:
 
【1】@Getter  @Setter
    见文思意,为变量赋予get、set方法。默认生成public方法,我们也可以指定访问权限等级:
public class DD {
 
    @Getter @Setter(AccessLevel.PUBLIC)
    private String lombok;
}
 
若注解在类上,则默认所有非静态的变量都生成get、set方法,同样可以通过注释来实现禁止生成的功能:
@Setter
@Getter
public class DD {
 
    @Setter(AccessLevel.NONE)
    private String lombok;
}
 
Lombok实际开发中的使用和理解工作原理
 
 
【2】@ToString
    该注解应用在类上,为我们生成Object的toString方法,而该注解里面的几个属性能更加丰富我们想要的内容,exclude属性禁止在toString方法中使用某字段,而of可以指定需要使用的字段:
@Getter
@Setter
@ToString(exclude = {"a", "b"}, of = {"c", "lombok"})
public class DD {
 
    private Integer a;
 
    private Integer b;
 
    private Integer c;
 
    private String lombok;
 
    public static void main(String[] args) {
        DD dd = new DD();
        System.out.println(dd.toString());
    }
}
 
DD(c=null, lombok=null)
 
 
【3】@EqualsAndHashCode
    该注解应用在类上,使用该注解,lombok会为我们生成equals(Object other)和hashCode()方法,包括所有非静态属性和非transient的属性;同样可以使用exclude属性禁止在toString方法中使用某字段,而of可以指定需要使用的字段;也可以通过callSuper属性在重写的方法中使用父类的字段,这样我们可以更加灵活的定义bean的比对,如下图:
 
 
【4】NonNull
    该注解应用在方法或构造器的参数上或者属性上,用来判断参数的合法性,默认抛出NPE异常:
@Getter
@Setter
@ToString
public class DD {
 
    private Integer a;
 
    private Integer b;
 
    private Integer c;
 
    private String lombok;
 
    public DD(@NonNull Integer a, Integer b, Integer c, String lombok) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.lombok = lombok;
    }
 
    public static void main(String[] args) {
        DD a1 = new DD(1, 2, 3, "a");
        DD a2 = new DD(null, 2, 3, "a");
        System.out.println(a1.toString()+"\n"+a2.toString());
    }
}
Lombok实际开发中的使用和理解工作原理
 
 
【5】NoArgsConstructor、RequiredArgsConstructor、AllArgsConstructor
    以上三个注解分别为我们生成无参构造器、指定参数构造器和包含所有参数的构造器。默认情况下,RequiredArgsConstructor、AllArgsConstructor生成的构造函数会对@NonNull注解的属性进行非空校验。
 
无参不说了,很好理解,主要说明后面两种的使用:
(1)RequiredArgsConstructor:
    主要对于字段类型是final和使用@NonNull注解标注的属性来生成构造函数;同时可以通过staticName来指定静态方法来构造对象。
@Getter
@Setter
@ToString
@RequiredArgsConstructor(staticName = "getStaticConstruct")
public class DD {
 
    @NonNull
    final private Integer a;
 
    @NonNull
    private Integer b;
 
    final private Integer c;
 
    private String lombok;
}
Lombok实际开发中的使用和理解工作原理
 
 
【6】Data
    Data注解集合了Getter、Setter、RequiredArgsConstructor、EqualsAndHashCode、ToString的合力体现:
@ToString
@Data(staticConstructor = "aaaa")
public class DD {
 
    @NonNull
    final private Integer a;
 
    @NonNull
    private Integer b;
 
    final private Integer c;
 
    private String lombok;
}
Lombok实际开发中的使用和理解工作原理
 
 
【7】Builder
    函数式编程或者说流式的编程越来越流行,应用在大多数语言中,让程序更具简洁,可读性更高,编写更连贯,@Builder就带来了这个功能,生成一系列的build API,该注解也需要应用在类上,看下面的例子就会更加清晰明了:
@ToString
@Data(staticConstructor = "aaaa")
@Builder
public class DD {
 
    @NonNull
    final private Integer a;
 
    @NonNull
    private Integer b;
 
    final private Integer c;
 
    private String lombok;
 
    public static void main(String[] args) {
        DD.builder().a(1).b(2).build();
    }
}
 
 
【8】Log
    该注解需要应用到类上,在编写服务层,需要添加一些日志,以便定位问题,我们通常会定义一个静态常量Logger,然后应用到我们想输出日志的地方,现在一个注解就可以实现:
@Data(staticConstructor = "aaaa")
@Builder
@Log
@AllArgsConstructor
public class DD {
 
    @NonNull
    final private Integer a;
 
    @NonNull
    private Integer b;
 
    final private Integer c;
 
    private String lombok;
 
    public DD(@NonNull Integer a, @NonNull Integer b, Integer c) {
        log.info("ppppp");
        this.a = a;
        this.b = b;
        this.c = c;
    }
 
    public static void main(String[] args) {
        DD b = new DD(1,2,3);
        System.out.println(b);
    }
}
Lombok实际开发中的使用和理解工作原理
 
 
【9】val
    熟悉JS的同学都知道,var可以定义任何类型的变量,而在JAVA的视线中,我们需要制定具体变量的类型,而val让我们摆脱指定,编译后就精确匹配上类型,默认是final类型,就像java8中的函数式表达一样,()->System.out.println(“hello lombok”); 就可以解析到Runnable函数式接口。
 
JAVA文件:
var map = new HashMap<String,String>();
map.put("1","2");
map.put("2","3");
map.put("3","4");
System.out.println(map.toString());
 
CLASS文件:
public static void main(String[] args) {
    HashMap<String, String> map = new HashMap();
    map.put("1", "2");
    map.put("2", "3");
    map.put("3", "4");
    System.out.println(map.toString());
}
 
    编译后,可以精确的定位类型。
 
 
【9】CleanUp
    当我们对流进行操作,我们通常需要调用close方法来关闭或结束某资源,而@CleanUp注解可以帮我们调用close方法,并且放到try/catch块中进行处理,如下图:
 
JAVA文件:
public static void main(String[] args) {
    try {
        @Cleanup var inputStream = new FileInputStream(args[0]);
        inputStream.read();
    } catch (Exception e) {
        e.printStackTrace();
    }
 
}
 
CLASS文件:
public static void main(String[] args) {
    try {
        FileInputStream inputStream = new FileInputStream(args[0]);
 
        try {
            inputStream.read();
        } finally {
            if (Collections.singletonList(inputStream).get(0) != null) {
                inputStream.close();
            }
 
        }
    } catch (Exception var6) {
        var6.printStackTrace();
    }
 
}
 
 
其实在 JDK1.7 之后就有了 try-with-resource,不用我们显式的关闭流:
 
public class AA implements AutoCloseable{
 
    public void ss(){
        System.out.println("ss");
    }
 
    @Override
    public void close() throws Exception {
        System.out.println("close");
    }
}
 
// 调用aa,测试关闭
public static void main(String[] args) {
    try (var aa = new AA()) {
        aa.ss();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
 
结果:
ss
close
 
 
  • 总结
 
    Lombok的基本操作流程是这样的:
  1. 定义编译期的注解
  2. 利用JSR269 api(Pluggable Annotation Processing API )创建编译期的注解处理器
  3. 利用tools.jar的javac api处理AST(抽象语法树)
  4. 将功能注册进入jar包
 
流程图:
Lombok实际开发中的使用和理解工作原理