Java开发神器Lombok的使用与原理

简介: Java开发神器Lombok的使用与原理

在面向对象编程中必不可少需要在代码中定义对象模型,而在基于Java的业务平台开发实践中尤其如此。相信大家在平时开发中也深有感触,本来是没有多少代码开发量的,但是因为定义的业务模型对象比较多,而需要重复写Getter/Setter、构造器方法、字符串输出的ToString方法和Equals/HashCode方法等。那么是否一款插件或工具能够替大家完成这些繁琐的操作呢?本文将向大家介绍一款在Eclipse/Intellij IDEA主流的开发环境中都可以使用的Java开发神器,同时简要地介绍下其背后自定义注解的原理。

Lombok的简介

Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一些注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的Java模型对象(POJO)。在开发环境中使用Lombok插件后,Java开发人员可以节省出重复构建,诸如hashCode和equals这样的方法以及各种业务对象模型的accessor和ToString等方法的大量时间。对于这些方法,它能够在编译源代码期间自动帮我们生成这些方法,并没有如反射那样降低程序的性能。

在Intellij中安装Lombok的插件

想要体验一把Lombok的话,得先在自己的开发环境中安装上对应的插件。下面先为大家展示下如何在Intellij中安装上Lombok插件。

通过IntelliJ的插件中心寻找Lombok

image.png

从Intellij插件中心安装Lombok

image.png

另外需要注意的是,在使用lombok注解的时候记得要导入lombok.jar包到工程,如果使用的是Maven的工程项目的话,要在其pom.xml中添加依赖如下:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.8</version>
</dependency>

好了,就这么几步后就可以在Java工程中开始用Lombok这款开发神器了。下文将会给大家介绍Lombok中一些注解的使用方法,让大家对如何用这些注解有一个大致的了解。

Lombok注解使用方法

Lombok常用注解介绍

下面先来看下Lombok中主要几个常用注解介绍:

image.png

Lombok的基本使用示例

(1)Val可以将变量申明是final类型。

public   static void main(String[] args) {
    val setVar = new HashSet<String>();
    val listsVar = new   ArrayList<String>();
    val mapVar = new HashMap<String,   String>();
    //=>上面代码相当于如下:
    final Set<String> setVar2 = new   HashSet<>();
    final List<String> listsVar2 = new   ArrayList<>();
    final Map<String, String> maps2 =   new HashMap<>();
}

(2)@NonNull注解能够为方法或构造函数的参数提供非空检查。

public void notNullExample(@NonNull String string) {
    //方法内的代码
}
//=>上面代码相当于如下:
public void notNullExample(String string) {
    if (string != null) {
        //方法内的代码相当于如下:
    } else {
        throw new NullPointerException("null");
    }
}

(3)@Cleanup注解能够自动释放资源。

public   void jedisExample(String[] args) {
    try {
        @Cleanup Jedis jedis =   redisService.getJedis();
    } catch (Exception ex) {
        logger.error(“Jedis异常:”,ex)
    }
    //=>上面代码相当于如下:
    Jedis jedis= null;
    try {
        jedis = redisService.getJedis();
    } catch (Exception e) {
        logger.error(“Jedis异常:”,ex)
    } finally {
        if (jedis != null) {
            try {
                jedis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

4)@Getter/@Setter注解可以针对类的属性字段自动生成Get/Set方法。

public class OrderCreateDemoReq{
    @Getter
    @Setter
    private String customerId;
    @Setter
    @Getter
    private String poolId;
    //其他代码……
}
//上面请求Req类的代码相当于如下:
public class OrderCreateDemoReq{
    private String customerId;    
    private String poolId;
    public String getCustomerId(){
         return customerId;
    }
    public String getPoolId(){
         return poolId;
    }
    public void setCustomerId(String customerId){
         this.customerId = customerId;
    }
    public void setPoolId(String poolId){
         this.pool = pool;
    }
}

(5)@ToString注解,为使用该注解的类生成一个toString方法,默认的toString格式为:ClassName(fieldName= fieleValue ,fieldName1=fieleValue)。


@ToString(callSuper=true,exclude="someExcludedField")
public   class Demo extends Bar {
    private boolean someBoolean = true;
    private String someStringField;
    private float someExcludedField;
}
//上面代码相当于如下:
public   class Demo extends Bar {
    private boolean someBoolean = true;
    private String someStringField;
    private float someExcludedField;
    @ Override
    public String toString() {
        return "Foo(super=" +   super.toString() +
            ", someBoolean=" +   someBoolean +
            ", someStringField=" +   someStringField + ")";
    }
}

(6)@EqualsAndHashCode注解,为使用该注解的类自动生成equals和hashCode方法。

@EqualsAndHashCode(exclude = {"id"}, callSuper =true)
public class LombokDemo extends Demo{
    private int id;
    private String name;
    private String gender;
}
//上面代码相当于如下:
public class LombokDemo extends Demo{
    private int id;
    private String name;
    private String gender;
    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (o == null) return false;
        if (o.getClass() != this.getClass()) return false;
        if (!super.equals(o)) return false;
        final LombokDemo other = (LombokDemo)o;
        if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
        if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false;
        return true;
    }
    @Override
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = result * PRIME + super.hashCode();
        result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
        result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode());
        return result;
    }

(7) @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor,这几个注解分别为类自动生成了无参构造器、指定参数的构造器和包含所有参数的构造器。

@RequiredArgsConstructor(staticName = "of") 
@AllArgsConstructor(access = AccessLevel.PROTECTED) 
public class ConstructorExample<T> { 
  private int x, y; 
  @NonNull private T description; 
  @NoArgsConstructor 
  public static class NoArgsExample { 
    @NonNull private String field; 
  } 
}
//上面代码相当于如下:
@RequiredArgsConstructor(staticName = "of") 
@AllArgsConstructor(access = AccessLevel.PROTECTED) 
public class ConstructorExample<T> { 
  private int x, y; 
  @NonNull private T description; 
  @NoArgsConstructor 
  public static class NoArgsExample { 
    @NonNull private String field; 
  } 
}
public class ConstructorExample<T> { 
  private int x, y; 
  @NonNull private T description; 
  private ConstructorExample(T description) { 
    if (description == null) throw new NullPointerException("description"); 
    this.description = description; 
  } 
  public static <T> ConstructorExample<T> of(T description) { 
    return new ConstructorExample<T>(description); 
  } 
  @java.beans.ConstructorProperties({"x", "y", "description"}) 
  protected ConstructorExample(int x, int y, T description) { 
    if (description == null) throw new NullPointerException("description"); 
    this.x = x; 
    this.y = y; 
    this.description = description; 
  } 
  public static class NoArgsExample { 
    @NonNull private String field;
    public NoArgsExample() { 
    } 
  } 
}

(8)@Data注解作用比较全,其包含注解的集合@ToString@EqualsAndHashCode,所有字段的@Getter和所有非final字段的@Setter, @RequiredArgsConstructor。其示例代码可以参考上面几个注解的组合。

(9)@Builder注解提供了一种比较推崇的构建值对象的方式。

@Builder 
public class BuilderExample { 
  private String name; 
  private int age; 
  @Singular private Set<String> occupations; 
}
//上面代码相当于如下:
public class BuilderExample { 
  private String name; 
  private int age; 
  private Set<String> occupations; 
  BuilderExample(String name, int age, Set<String> occupations) { 
    this.name = name; 
    this.age = age; 
    this.occupations = occupations; 
  } 
  public static BuilderExampleBuilder builder() { 
    return new BuilderExampleBuilder(); 
  } 
  public static class BuilderExampleBuilder { 
    private String name; 
    private int age; 
    private java.util.ArrayList<String> occupations;    
    BuilderExampleBuilder() { 
    } 
    public BuilderExampleBuilder name(String name) { 
      this.name = name; 
      return this; 
    } 
    public BuilderExampleBuilder age(int age) { 
      this.age = age; 
      return this; 
    } 
    public BuilderExampleBuilder occupation(String occupation) { 
      if (this.occupations == null) { 
        this.occupations = new java.util.ArrayList<String>(); 
      } 
      this.occupations.add(occupation); 
      return this; 
    } 
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations) { 
      if (this.occupations == null) { 
        this.occupations = new java.util.ArrayList<String>(); 
      } 
      this.occupations.addAll(occupations); 
      return this; 
    } 
    public BuilderExampleBuilder clearOccupations() { 
      if (this.occupations != null) { 
        this.occupations.clear(); 
      }
      return this; 
    } 
    public BuilderExample build() {  
      Set<String> occupations = new HashSet<>(); 
      return new BuilderExample(name, age, occupations); 
    } 
    @verride 
    public String toString() { 
      return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")"; 
    } 
  } 
}

(10)@Synchronized注解类似Java中的Synchronized 关键字,但是可以隐藏同步锁。

public class SynchronizedExample { 
 private final Object readLock = new   Object(); 
 @Synchronized 
 public static void hello() { 
     System.out.println("world");   
 } 
 @Synchronized("readLock") 
 public void foo() { 
   System.out.println("bar"); 
 } 
//上面代码相当于如下:
 public class SynchronizedExample { 
  private static final Object $LOCK = new   Object[0]; 
  private final Object readLock = new   Object(); 
  public static void hello() { 
    synchronized($LOCK) { 
      System.out.println("world"); 
    } 
  }   
  public void foo() { 
    synchronized(readLock) { 
        System.out.println("bar");   
    } 
  } 
}

Lombok背后的自定义注解原理

本文在前三章节主要介绍了Lombok这款Java开发利器中各种定义注解的使用方法,但作为一个Java开发者来说光了解插件或者技术框架的用法只是做到了“知其然而不知其所以然”,如果真正掌握其背后的技术原理,看明白源码设计理念才能真正做到“知其然知其所以然”。好了,话不多说下面进入本章节的正题,看下Lombok背后注解的深入原理。

可能熟悉Java自定义注解的同学已经猜到,Lombok这款插件正是依靠可插件化的Java自定义注解处理API(JSR 269: Pluggable Annotation Processing API)来实现在Javac编译阶段利用“Annotation Processor”对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件”。有兴趣的同学反编译带有Lombok注解的类文件也就一目了然了。其大致执行原理图如下:

image.png

从上面的这个原理图上可以看出Annotation Processing是编译器在解析Java源代码和生成Class文件之间的一个步骤。其中Lombok插件具体的执行流程如下:

image.png

从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。使用Annotation Processing自定义注解是在编译阶段进行修改,而JDK的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。

需要更加深入理解Lombok插件的细节,自己查阅其源代码是必比可少的。对开源框架代码比较有执着追求的童鞋可以将Lombok的源代码工程从github上download到本地进行阅读和自己调试。下图为Lombok工程源代码的截图:

image.png

从熟悉JSR 269: Pluggable Annotation Processing API的同学可以从工程类结构图中发现AnnotationProcessor这个类是Lombok自定义注解处理的入口。该类有两个比较重要的方法一个是init方法,另外一个是process方法。在init方法中,先用来做参数的初始化,将AnnotationProcessor类中定义的内部类(JavacDescriptor、EcjDescriptor)先注册到ProcessorDescriptor类型定义的列表中。其中,内部静态类—JavacDescriptor在其加载的时候就将lombok.javac.apt.LombokProcessor这个类进行对象实例化并注册。

LombokProcessor处理器中,其中的process方法会根据优先级来分别运行相应的handler处理类。Lombok中的多个自定义注解都分别有对应的handler处理类,如下图所示:

image.png

可以看出,在Lombok中对于其自定义注解进行实际的替换、修改和处理的正是这些handler类。对于其实现的细节可以具体参考其中的代码。

本文先从Lombok使用角度出发,先介绍了如何在当前主流的Java开发环境—Intellij中安装这块Java插件,随后分别介绍了Lombok中几种主要的常用注解(比如,@Data@CleanUp@ToString@Getter/Setter等),最后将Lombok背后Java自定义注解的原理与源代码结合起来,介绍了Lombok插件背后具体的执行处理流程。限于篇幅,未能对“自定义Java注解处理器”的具体实践和原理进行阐述,后面将另起专题篇幅进行介绍。限于笔者的才疏学浅,对本文内容可能还有理解不到位的地方,如有阐述不合理之处还望留言一起探讨。


目录
相关文章
|
9天前
|
Java API Maven
如何使用Java开发抖音API接口?
在数字化时代,社交媒体平台如抖音成为生活的重要部分。本文详细介绍了如何用Java开发抖音API接口,从创建开发者账号、申请API权限、准备开发环境,到编写代码、测试运行及注意事项,全面覆盖了整个开发流程。
52 10
|
14天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
15天前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
17天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
15天前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
33 4
|
17天前
|
缓存 监控 Java
如何运用JAVA开发API接口?
本文详细介绍了如何使用Java开发API接口,涵盖创建、实现、测试和部署接口的关键步骤。同时,讨论了接口的安全性设计和设计原则,帮助开发者构建高效、安全、易于维护的API接口。
44 4
|
22天前
|
SQL Java 程序员
倍增 Java 程序员的开发效率
应用计算困境:Java 作为主流开发语言,在数据处理方面存在复杂度高的问题,而 SQL 虽然简洁但受限于数据库架构。SPL(Structured Process Language)是一种纯 Java 开发的数据处理语言,结合了 Java 的架构灵活性和 SQL 的简洁性。SPL 提供简洁的语法、完善的计算能力、高效的 IDE、大数据支持、与 Java 应用无缝集成以及开放性和热切换特性,能够大幅提升开发效率和性能。
|
16天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
31 0
WK
|
22天前
|
开发框架 移动开发 Java
C++和Java哪个更适合开发移动应用
本文对比了C++和Java在移动应用开发中的优劣,从市场需求、学习难度、开发效率、跨平台性和应用领域等方面进行了详细分析。Java在Android开发中占据优势,而C++则适合对性能要求较高的场景。选择应根据具体需求和个人偏好综合考虑。
WK
39 0
|
IDE Java API
Lombok:让JAVA代码更优雅
Lombok:让JAVA代码更优雅
107 0
Lombok:让JAVA代码更优雅
下一篇
无影云桌面