你有没有掉进去过这些 Lombok 的 “陷阱“

一、Lombok 工具

Lombok 项目是一个 Java 库,它会自动插入编辑器和构建工具中,Lombok 提供了一组有用的注解,用来消除 Java 类中的大量样板代码。通过添加注解就可以替换数百行代码从而产生干净,简洁且易于维护的Java 类。

Lombok 中常用注解有 @Getter/@Setter、@ToString、@Data、@NoArgsConstructor 和 @Slf4j,其中最最常用的就是 @Data 注解,该注解可以生成 getter/setter、equals、hashCode 以及 toString 等方法。

Lombok 原理

image.png

Lombok本质上就是一个实现了 JSR 269 API的程序,使用 javac 进行编译时,生成目标方法的流程如下:

  1. 首先 javac 对源代码进行分析生成一棵抽象语法树(AST)
  2. 接着在运行过程中调用实现了 JSR 269 API 的 lombok 程序
  3. 接着编译器会调用 lombok 程序对上面得到的抽象语法树 AST 进行处理,找到其注解所在类对应的语法树(AST),然后修改该语法树,增加注解对应的方法或代码片段到定义的相应树节点
  4. javac 使用修改后的抽象语法树生成最终的 class 文件

更多 Lombok 的注解

关于 Lombok 更多注解的使用方式可以参考这两篇文章

Lombok 中存在的陷阱

新建 maven 项目 lombok-traps 并添加 Lombok 依赖、Junit 依赖等

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.18</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
    <scope>test</scope>
</dependency>
复制代码

在 entity 包中新建一个 Tesla 实体类,并添加 @Data 注解

@Data
public class Tesla {

    private String tName;
    private String tFactory;
    private String vehicleType;
    private Double vehiclePrice;
}
复制代码

Lombok 解析单个小写字母的陷阱

对于 Lombok 生成的单个小写字母驼峰命名属性的 getter/setter 方法在进行序列化反序列化的时候是无法被 JSON 工具以及 Spring 识别的,从而导致数据丢失

首先在 lombok-traps 项目的 pom.xml 文件中导入 jackson 的依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>
复制代码

在 test 包下新增测试类 TeslaTest,增加一个测试方法 testSingleLowerLetter

public class TeslaTest {

    @Test
    public void testSingleLowerLetter() throws JsonProcessingException {
        // ObjectMapper 进行序列化和反序列化
        ObjectMapper mapper = new ObjectMapper();

        String teslaJson = "{"vehicleType":"跑车","vehiclePrice":1200000.0,"tFactory":"上海特斯拉超级工厂","tName":"Roadster 2.0"}";

        // 反序列化,将teslaJSON 转换成 tesla 对象
        Tesla tesla = mapper.readValue(teslaJson, Tesla.class);
        System.out.println("转换的 tesla 对象为:" + tesla);
    }
}
复制代码

执行上述代码,输出结果如下:

image.png

该报错提示 tFactory 是无法识别的属性,说明 Lombok 生成的 getter/setter 方法中与预想的有些不一致,可以在 TeslaTest 中在增加一个方法,将一个 Tesla 对象序列化,

@Test
public void testerialize() throws JsonProcessingException {
    // ObjectMapper 进行序列化和反序列化
    ObjectMapper mapper = new ObjectMapper();

    Tesla tesla = new Tesla();
    tesla.setTName("Roadster 2.0");
    System.out.println(mapper.writeValueAsString(tesla));
}
复制代码

执行上述代码,输出结果如下:

image.png

对于单字母驼峰命名的属性 Lombok 在生成 getter/setter 方法时会将属性名字母全部编程小写,导致在反序列化时报错。

可以把这种单个小写字母开头的驼峰命名的属性全部改为小写,再次测试

@Test
public void testSingleLowerLetter() throws JsonProcessingException {

    // 其余代码保持不变
    
    String teslaJson = "{"vehicleType":"跑车","vehiclePrice":1200000.0,"tfactory":"上海特斯拉超级工厂","tname":"Roadster 2.0"}";
    
}
复制代码

执行 testSingleLowerLetter 测试方法的输出结果如下:

转换的 tesla 对象为:Tesla(tName=Roadster 2.0, tFactory=上海特斯拉超级工厂, vehicleType=跑车, vehiclePrice=1200000.0)
复制代码

反序列化成功,其实这是由于命名规范导致的一个问题,在编码中要尽量避免这种首字母小写第二个字母大写的这种命名规范。

equals 方法和 hashCode 方法的陷阱

在 entity 包下定义一个 Factory 类并添加 @Data、@NoArgsConstructor 和 @AllArgsConstructor 注解。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Factory {

    private Integer id;
    private String factoryName;
}
复制代码

在 entity 包下定义一个 ShanghaiGigaFactory 类继承 Factory 类,同时类上也标注同样的三个注解。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ShanghaiGigaFactory extends Factory {

    private String address;
    private Integer productivity;

    public ShanghaiGigaFactory(Integer id, String factoryName, String address, Integer productivity) {
        super(id, factoryName);
        this.address = address;
        this.productivity = productivity;
    }

}
复制代码

在 test 包下新建测试类并新建一个测试方法 testEqualsAndHashCodeGenerateByLombok

public class FactoryTest {

    @Test
    public void testEqualsAndHashCodeGenerateByLombok(){
        GigaFactory shanghaiGigaFactory = new GigaFactory(
                4, "Shanghai GigaFactory","Shanghai, China", 5000000
        );

        GigaFactory shanghaiGigaFactory2 = new GigaFactory(
                3, "Shanghai GigaFactory 2","Shanghai, China", 5000000
        );

        System.out.println(shanghaiGigaFactory.equals(shanghaiGigaFactory2));
    }
}
复制代码

执行该测试方法,输出结果如下: image.png

输出结果为 false 而非 true,说明只比较了子类 GigaFactory 中的 address 属性和 productivity 属性的值是否相同,而没有比较父类的 id 属性和 name 属性。

这是因为 Lombok 的 @Data 和 @EqualsAndHashCode 注解在生成 equals 方法和 hashCode 方法时默认不会将父类的属性进行比较,只会比较子类的属性,这是由 @EqualsAndHashCode 的 callSuper 属性控制的,该属性默认为 false。

在 GigaFactory 类上增加注解 @EqualsAndHashCode(callSuper = true),此时再次执行测试类中的 testEqualsAndHashCodeGenerateByLombok 方法,运行结果如下: image.png 输出结果符合预期。

猜你喜欢

转载自juejin.im/post/7110609843689357326