• Lombok常用方法及原理介绍


    ⼀种简化源码提⾼编程效率的⼯具,⽤于⽣成常⽤的代码。

    两个包:

    • lombok

    • lombok.experimental (实验的特性)

    如何使用lombok

    引⼊依赖 

    1.             <dependency>
    2.                 <groupId>org.projectlombokgroupId>
    3.                 <artifactId>lombokartifactId>
    4.                 <version>${lombok.version}version>
    5.             dependency>

    安装插件 

    代码中使⽤

    1. @Data
    2. public class People {
    3.     private String name;
    4. }

    常用注解汇总

    @val

    声明变量时能自行推断变量类型, 并且自带 final 属性,

    @Data

    @ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor

    @Slf4j

    生成 log 对象

    @Value

    与 @Data 类似, 有以下两点区别:

    • 生成的是全参的构造器;

    • 只有 getter 方法, 没有 setter 方法;

    @Getter

    所有属性的Getter方法

    @Setter

    所有属性的Setter方法

    @Builder

    生成链式构造器的代码

    @Cleanup

    安全释放或者关闭资源, 最常见的场景就是 IO 中关闭流的操作

    @NonNull

    getter : 如果获取出来属性为 null, 则抛出 NPE

    setter : 如果设置属性时, 传入的值为 null, 则抛出 NPE

    @ToString

    生成 toString() 方法

    @SneakyThrows

    将抛出的异常吃掉, 减少一些不必要的 try catch 代码

    @NoArgsConstructor

    生成一个无参构造方法

    @AllArgsConstructor

    添加一个包含所有属性的Constructor

    @EqualsAndHashCode

    生成 equals() 和 hashcode() 方法

    @RequiredArgsConstrutor

    会生成一个包含常量,和标识了NotNull的变量的构造方法

    案例

    被注释的部分是lombok会帮助我们自动生成的代码。

    @Slf4j

    1. @Slf4j
    2. public class Test {
    3.     // private static final Logger log=LoggerFactory.getLogger(Test.class);
    4.     public static void main(String[] args) {
    5.         log.info("Hello world");
    6.     }
    7. }

    @Builder

    1.         Test test = Test.builder()
    2.                 .id(id)
    3.                 .page(page)
    4.                 .build();
    5. @Data
    6. @Builder
    7. public class Test {
    8.     private String id;
    9.     private String page;
    10.     
    11.     /**
    12.      @java.beans.ConstructorProperties({"id", "page"})
    13.     Test(Long id, int page) {
    14.         this.id = id;
    15.         this.page = page;
    16.     }
    17.     public static TestBuilder builder() {
    18.         return new TestBuilder();
    19.     }
    20.     public TestBuilder toBuilder() {
    21.         return new TestBuilder().id(this.id).page(this.page);
    22.     }
    23.     public static class TestBuilder {
    24.         private Long id;
    25.         private int page;
    26.         TestBuilder() {}
    27.         public TestBuilder id(Long id) {
    28.             this.id = id;
    29.             return this;
    30.         }
    31.         public TestBuilder page(int page) {
    32.             this.page = page;
    33.             return this;
    34.         }
    35.         public Test build() {
    36.             return new Test(id, page);
    37.         }
    38.         public String toString() {
    39.             return "Test.TestBuilder(id=" + this.id + ", page="
    40.                 + this.page")";
    41.         }
    42.     */
    43. }

    @SneakyThrows

    1.     @Test(expected = RuntimeException.class)
    2.     @SneakyThrows
    3.     public void test_throw_exception() {
    4.         when(HttpClientUtil.get(anyString()).thenThrow(new RuntimeException());
    5.         api.test("nice");
    6.     }

    @Data

    1. @Data
    2. public class User {
    3.     private Long id;
    4.     private String username;
    5.     private String password;
    6.     
    7.     
    8.     /**
    9.     public User() {}
    10.     public Long getId() {return this.id;}
    11.     public String getUsername() {return this.username;}
    12.     public String getPassword() {return this.password;}
    13.     public void setId(Long id) {this.id = id; }
    14.     public void setUsername(String username) {this.username = username; }
    15.     public void setPassword(String password) {this.password = password; }
    16.     public boolean equals(Object o) {
    17.         if (o == this) { return true; }
    18.         ...
    19.         return true;
    20.     }
    21.     public int hashCode() {
    22.         final int PRIME = 59;
    23.         int result = 1;
    24.         final Object $id = this.getId();
    25.         result = result * PRIME + ($id == null ? 43 : $id.hashCode());
    26.         final Object $username = this.getUsername();
    27.         ...
    28.         return result;
    29.     }
    30.     protected boolean canEqual(Object other) {return other instanceof User;}
    31.     public String toString() {
    32.         return "User(id=" + this.getId() + ...+ ")";
    33.     }
    34.     */
    35. }
    36. }

    @Value

    1. @Value
    2. public class Test {
    3.     (private final) Long id;
    4.     (private final) String page;
    5.     
    6.     /**
    7.     @java.beans.ConstructorProperties({"id", "page"})
    8.     public Test(Long id, String page) {
    9.         this.id = id;
    10.         this.page = page;
    11.     }
    12.     public Long getId() {return this.id;}
    13.     public String getPage() {return this.page;}
    14.     public boolean equals(Object o) {
    15.         if (o == this) { return true; }
    16.           ...
    17.         return true;
    18.     }
    19.     public int hashCode() {
    20.         final int PRIME = 59;
    21.         int result = 1;
    22.         final Object $id = this.getId();
    23.         result = result * PRIME + ($id == null ? 43 : $id.hashCode());
    24.         ...
    25.         return result;
    26.     }
    27.     public String toString() {
    28.             return "Test.TestBuilder(id=" + this.id + ", page="
    29.                 + this.page")";
    30.         }
    31.     */
    32. }

    Java代码规约

    • 【推荐】字段偏多或构造函数参数偏多的不可变实体类推荐使用lombok来生成builder

    • 【推荐】在Record类型数据支持之前,推荐实体类字段用lombok注解来生成setter和getter,并根据最小化原则,来决定是否只用@Getter、@Setter还是@Data

    • 【推荐】合约式编程时,推荐使用IDEA IntelliJ > Spring > Lombok的注解的非空注解来说明参数非空

    优劣

    优点

    • 有效的减少代码量

    • 动态。例如在增减字段时,⽆需再操⼼Getter/Setter等⽅法的修改 

    • 在⼀定程度上提⾼了代码的可读性

    缺点

    • 引⼊外部依赖,一旦在resource的包里使用了lombok,别人想看你的源码也不得不安装插件

    • ⽣成的代码不直观,可能不按预期⽣成代码

    • 降低了源码的完整性

    常见问题

    @Builder

    属性默认值问题。如果使用 @Builder 生成代码, 属性的默认值会失效, 需要使用 @Builder.Default 标记有默认值的属性, 比如:

    1.     @Builder
    2.     public class UserQueryParam {
    3.         private Long id;
    4.         private String username;
    5.         @Builder.Default
    6.         private int page = 1;
    7.         @Builder.Default
    8.         private int size = 15;
    9.     }

    @Getter

    对布尔值的特殊处理

    private boolean test;

    1.读取属性的方法默认生成规则是is+属性名而不是get+属性名,即isTest而不是getTest

    2.假如属性名自身以is开头,如isTest,则获取属性的方法依旧是isTest,而不是别扭的isIsTest

    3.以上两条规则,会出现一种因为不合理导致的极端情况,即有两个属性,一个名字是isTest,另一个是test,这里本质上是设计存在问题,对于这种情况,插件的处理方式是只生成一个isTest,至于读取的是哪个属性,取决于属性的顺序,居前者优先.

    @Value 与@Data

    @Value常用于不可变类。不可变类是指创建该类的实例后,该实例的实例变量是不可改变的。

    与 @Data 类似, 主要有以下两点区别:

    • 生成的是全参的构造器;

    • 只有 getter 方法, 没有 setter 方法;

    @Value

    @Data

    @Getter

    @Setter

    @ToString

    @EqualsAndHashCode

    @RequiredArgsConstrutor

    @AllArgsConstructor 

    @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE)

    被@Value标识的注解不能与@RequestBody、jackson同时使用。

    使用@Value后jackson反序列化失败而fastjson可以成功的原因:

     1、jackson读取的是set方法,@Value没有生成set方法,所以失败; 

    2、fastjson读取的是构造函数,@Value生成了构造函数,所以成功; 

    扩展:使用fastjson时,使用@Builder,要检查下有没有加上@NoArgsConstructor或者@AllArgsConstructor

    Lombok原理

    在编译时修改抽象语法树(AST)

    1. @Getter(AccessLevel.PUBLIC)
    2. public class User {
    3.     private Long id;
    4.     private String username;
    5.     @Getter(AccessLevel.PRIVATE)
    6.     private String password;
    7. }
    1. public class User {
    2.     private Long id;
    3.     private String username;
    4.     private String password;
    5.     public Long getId() {return this.id;}
    6.     public String getUsername() {return this.username;
    7.     // Field 优先级更高
    8.     private String getPassword() {return this.password;}
    9. }

    AST语法树

    介绍

    Lombok是在AST语法树环节处理,AST是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构,例如包、类型、修饰符、运算符、接口、返回值,甚至代码注释等都可以是一个语法结构。

    样例

    在Idea上,也可以通过安装插件查看代码的AST语法树,如下图所示

    左边的每个属性,每个方法,都能在右侧找到对应的节点,因此通过操作AST树的节点,即可完成在编译期的代码动态添加。

    JSR 269 

    自Java 6起,javac开始支持 JSR 269 Pluggable Annotation Processing API 规范,只要程序实现了该API,就能在java源码编译时调用定义的注解。Lombok的本质是依靠 JSR 269 来实现在Javac编译阶段利用“Annotation Processor”(注解处理工具)对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件”。

    从上面的这个原理图上可以看出Annotation Processing是编译器在解析Java源代码和生成Class文件之间的一个步骤。

    lombok

    Lombok本质上就是一个实现了“JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下::

    1. javac对源代码进行分析,生成了一棵抽象语法树(AST)

    2. 运行过程中调用实现了“JSR 269 API”的Lombok程序

    3. 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点

    4. javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)

    从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。

    核心代码

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

    lombok插件

    使用Lombok注解省略的方法在被调用时,会报找不到定义的错误,此种情况下,需要做些特殊处理,如Intellij Idea中,需要下载安装Lombok plugin插件。

  • 相关阅读:
    如何利用Xshell远程连接Linux服务器
    408王道数据结构强化——应用题
    Django + Celery 实现异步查询数据库
    pandas使用pd.DatetimeIndex函数将字符串日期列表数据转化为时间索引数据DatetimeIndex
    5、垃圾收集器
    算法设计与分析 SCAU19180 集合划分问题
    Alkyne-PEG4-phosphoramidite,炔基-PEG4-磷酰胺,CAS: 1682657-14-2
    线上教育需要ICP备案还是许可?
    轻松学会结构栈
    现在的数字藏品该怎么玩才不会被割韭菜?
  • 原文地址:https://blog.csdn.net/xue_xiaofei/article/details/126145975