likes
comments
collection
share

Lombok正确使用姿势,你真的了解了吗?

作者站长头像
站长
· 阅读数 14

介绍

Project Lombok是一个java库,它可以自动插入编辑器和构建工具,通过Lombok我们不需要再在类上编写setter/getter、equals、try/catch、无参构造器、全参构造器 等方法。

Lombok features(特性)

Lombok包下注解:

Lombok属性注解特性说明
val用在局部变量前面,相当于将变量声明为 final
@NonNull给方法参数增加这个注解会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出NPE(NullPointerException)
@Cleanup自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成 try-finally 这样的代码来关闭流
@Getter/@Setter用在属性上,再也不用自己手写settergetter方法了,还可以指定访问范围
@ToString用在类上可以自动覆写toString方法,当然还可以加其他参数,例如 @ToString(exclude=“id”) 排除id属性,或者 @ToString(callSuper=true, includeFieldNames=true) 调用父类的toString方法,包含所有属性;
@EqualsAndHashCode用在类上自动生成equals方法和hashCode方法
@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有 @NonNull 属性作为参数的构造函数,如果指定 staticName="of" 参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多
@Data注解在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter@RequiredArgsConstrutor这些注解,对于POJO类十分有用
@Value用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法
@Builder用在类、构造器、方法上,为你提供复杂的 builder APIs,让你可以像如下方式一样调用Person.builder().name("xxx").city("xxx").build()
@SneakyThrows自动抛受检异常,而无需显式在方法上使用throws语句
@Synchronized用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性 LOCK,而Java中的synchronized关键字锁对象是 this,锁在this或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁this或者类对象,这可能会导致竞争条件或者其它线程错误
@Getter(lazy=true)可以替代经典的Double Check Lock样板代码
@Log根据不同的注解生成不同类型的log对象,但是实例名称都是log,有六种可选实现类 @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName()); @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class); @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class); @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class)

@SneakyThrows

一般都是标注在方法上面,通过@SneakyThrows我们不需要是手动写try{}...catch(Throwable e){}代码,Lombok会自动为我们生成,举个🌰:

@Override
@SneakyThrows
public String createOrder(CreateOrderDTO createOrderDTO) {
    boolean res = stockService.checkStock(createOrderDTO);
    if (res) {
        doCreateOrder(createOrderDTO);
    }
    return "create order success!";
}

最后编译生成的代码是这样的:

public String createOrder(CreateOrderDTO createOrderDTO) {
    try {
        boolean res = stockService.checkStock(createOrderDTO);
        if (res) {
            this.doCreateOrder(createOrderDTO);
        }
        return "create order success!";
    } catch (Throwable var3) {
        throw var3;
    }
}

@CleanUp

JAVA在实现IO流读写的场景,每次都要在finally里面关闭资源,这样会很让人头疼?那么有没有好的方法去生成这样的重复代码。方法有两种:一种是使用Lombok@Cleanup,另一种是使用jdk1.7+try-with-resources语法糖。我个人推荐使用try-with-resources语法糖,因为它是jdk提供的,所以受众更广,别人能更容易读懂你的代码,也不用绑定插件才能使用。在关闭流(资源)的时候,经常使用到以下代码:

public void cleanUpSample() {
    String filePath = "E://test.txt";
    FileReader reader = null;
    try {
        reader = new FileReader(filePath);
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } finally {
        try {
            // 有时候可能会忘记对文件资源close处理
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

当在处理文件对象,或者数据库资源时,我们总是会忘记close,可能引发内存溢出或者连接池用尽。如果手动去调用close方法,代码又会非常长,现在有了@Cleanup,我们不再需要担心这些问题。通过使用@Cleanup确保在代码执行路径退出当前作用域之前自动清除给定资源,举个例子:

public void cleanUpSample() {
    String filePath = "E://test.txt";
    try {
        @Cleanup FileReader reader = new FileReader(filePath);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

最后编译生成的代码是这样的:

public void cleanUpSample() {
    String filePath = "E://test.txt";

    try {
        FileReader reader = new FileReader(filePath);
        // @CleanUp注解实现自动为代码释放资源
        if (Collections.singletonList(reader).get(0) != null) {
            reader.close();
        }
    } catch (FileNotFoundException var3) {
        var3.printStackTrace();
    } catch (IOException var4) {
        var4.printStackTrace();
    }
}

Lombok.experimental包下注解:

Lombok注解特性说明
@Accessors一般的用法@Accessors(chain = true),当设置chaintrue的时候,set方法返回的是当前对象,则在进行属性赋值则可以连续进行,如:createOrderDTO.setProductId("1").setQuantity(1).setAmount(new BigDecimal("100"));
@UntilityClass使用@UtilityClass修饰类,类会被标记成final修饰类,属性会标记成静态属性,会生成一个私有的无参构造方法,并抛出一个UnsupportedOperationException异常,方法都会被标记成静态方法。
@Tolerate使用@Builder对一个DTO实现一个构造器,但是在做Json反序列化的时候发生错误,原因就是缺少无参公共的构造函数,而手动写一个无参构造函数的时候编译错误,就是和@Builder冲突,虽然标准的@Builder没法是需要私有化构造函数的,但是在某些场景下我们需要对这种标准变形,这个时候Lombok提供了@Tolerate实现对冲突的兼容。

@UntilityClass

@UntilityClass主要是针对工具类的一个注解,它的作用是,被@UntilityClass修饰的类它的所有成员变量都是静态的,可以直接通过ExampleClass.method()直接调用,省了创建实例对象的过程,举个例子:

@UtilityClass
public class GlobalWebUtils extends WebUtils {

    /**
     * 获取 HttpServletRequest
     *
     * @return {HttpServletRequest}
     */
    public Optional<HttpServletRequest> getRequest() {
        return Optional.ofNullable(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
    }

    /**
     * 获取 HttpServletResponse
     *
     * @return {HttpServletResponse}
     */
    public HttpServletResponse getResponse() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    }

}

使用工具类的时候,直接通过类调用:

@Override
@SneakyThrows
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response) {
    log.warn("表单登录失败:{}", exception.getMessage());
    String url = "http://xxx.com";
    // 使用工具类直接调用
    GlobalWebUtils.getResponse().sendRedirect(url);
}

Lombok使用注意点

1. Lombok中@Builder和@Data注解使用注意

  • @Builder使用设计模式中的创建者模式又叫建造者模式。简单来说,就是一步步创建一个对象,它对用户屏蔽了里面构建的细节,但却可以精细地控制对象的构造过程;
  • @Data注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()set()toString()等方法;

在项目中查询数据库后反射到实体类报错,找不到无参构造器。查看对应实体类上只使用@Builder@Data注解。

💥当@Data和@Builder 共同使用导致无参构造丢失

  • 单独使用@Data注解,会生成无参数构造方法;
  • 单独使用@Builder注解,生成全属性的构造方法,无无参构造方法;
  • @Data@Builder一起用:没有了默认的构造方法。手动添加无参数构造方法或者用@NoArgsConstructor注解会报错。

如果仅仅是使用@Data@Builder两个注解修饰实体类,编译之后生成的class文件:

public class CreateOrderDTO {

    private String productId;
    private Integer quantity;
    private BigDecimal amount;

    // (default类型) 全参构造器
    CreateOrderDTO(final String productId, final Integer quantity, final BigDecimal amount) {
        this.productId = productId;
        this.quantity = quantity;
        this.amount = amount;
    }
    
    // Builder
    public static CreateOrderDTOBuilder builder() {
        return new CreateOrderDTOBuilder();
    }

    public String getProductId() {
        return this.productId;
    }

    // equals()、hashCode()、GET/SET()等代码已省略

    public static class CreateOrderDTOBuilder {
        private String productId;
        private Integer quantity;
        private BigDecimal amount;

        CreateOrderDTOBuilder() {
        }

        public String toString() {
            return "CreateOrderDTO.CreateOrderDTOBuilder(productId=" + this.productId + ", quantity=" + this.quantity + ", amount=" + this.amount + ")";
        }
    }
}

可以看出如果是同时使用@Data@Builder的话,会生成属性的GET/SET方法,但是无参构方法没有了,对象缺少无参构造器是不可接受的,因为很多框架都会调用无参构造函数去创建对象。

如果这时候在实体类上添加无参构造函数,在项目运行会编译报错,此时可以手动添加一个有参函数,也可以在无参函数上添加@Tolerate注解实现无参构造器的生成。

Lombok正确使用姿势,你真的了解了吗?

无参构造函数加上@Tolerate注解才能编译成功!

Lombok正确使用姿势,你真的了解了吗?

如果是使用@Data@Builder@AllArgsConstructor三个注解修饰实体类,编译之后生成的class文件跟上面的一样,会生成无参构造器、全参构造器、Builder、getter&setter、equals、hashCode等方法,区别是全参构造器为public修饰符修饰

public CreateOrderDTO(final String productId, final Integer quantity, final BigDecimal amount) {
    this.productId = productId;
    this.quantity = quantity;
    this.amount = amount;
}

正常我们可以这样来定义一个实体类:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateOrderDTO {

    /**
     * 商品ID
     */
    private String productId;

    /**
     * 商品数量
     */
    private Integer quantity;

    /**
     * 订单金额
     */
    private BigDecimal amount;
}

最后编译测class文件 (无参构造器、全参构造器、GET/SET、equals、hashCode、toString、Builder等方法):

public class CreateOrderDTO {
    private String productId;
    private Integer quantity;
    private BigDecimal amount;

    public static CreateOrderDTOBuilder builder() {
        return new CreateOrderDTOBuilder();
    }

    public String getProductId() {
        return this.productId;
    }

    public Integer getQuantity() {
        return this.quantity;
    }

    public BigDecimal getAmount() {
        return this.amount;
    }

    public void setProductId(final String productId) {
        this.productId = productId;
    }

    public void setQuantity(final Integer quantity) {
        this.quantity = quantity;
    }

    public void setAmount(final BigDecimal amount) {
        this.amount = amount;
    }

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof CreateOrderDTO)) {
            return false;
        } else {
            CreateOrderDTO other = (CreateOrderDTO)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                label47: {
                    Object this$quantity = this.getQuantity();
                    Object other$quantity = other.getQuantity();
                    if (this$quantity == null) {
                        if (other$quantity == null) {
                            break label47;
                        }
                    } else if (this$quantity.equals(other$quantity)) {
                        break label47;
                    }

                    return false;
                }

                Object this$productId = this.getProductId();
                Object other$productId = other.getProductId();
                if (this$productId == null) {
                    if (other$productId != null) {
                        return false;
                    }
                } else if (!this$productId.equals(other$productId)) {
                    return false;
                }

                Object this$amount = this.getAmount();
                Object other$amount = other.getAmount();
                if (this$amount == null) {
                    if (other$amount != null) {
                        return false;
                    }
                } else if (!this$amount.equals(other$amount)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(final Object other) {
        return other instanceof CreateOrderDTO;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $quantity = this.getQuantity();
        result = result * 59 + ($quantity == null ? 43 : $quantity.hashCode());
        Object $productId = this.getProductId();
        result = result * 59 + ($productId == null ? 43 : $productId.hashCode());
        Object $amount = this.getAmount();
        result = result * 59 + ($amount == null ? 43 : $amount.hashCode());
        return result;
    }

    public String toString() {
        return "CreateOrderDTO(productId=" + this.getProductId() + ", quantity=" + this.getQuantity() + ", amount=" + this.getAmount() + ")";
    }

    public CreateOrderDTO() {
    }

    public CreateOrderDTO(final String productId, final Integer quantity, final BigDecimal amount) {
        this.productId = productId;
        this.quantity = quantity;
        this.amount = amount;
    }

    public static class CreateOrderDTOBuilder {
        private String productId;
        private Integer quantity;
        private BigDecimal amount;

        CreateOrderDTOBuilder() {
        }

        public CreateOrderDTOBuilder productId(final String productId) {
            this.productId = productId;
            return this;
        }

        public CreateOrderDTOBuilder quantity(final Integer quantity) {
            this.quantity = quantity;
            return this;
        }

        public CreateOrderDTOBuilder amount(final BigDecimal amount) {
            this.amount = amount;
            return this;
        }

        public CreateOrderDTO build() {
            return new CreateOrderDTO(this.productId, this.quantity, this.amount);
        }

        public String toString() {
            return "CreateOrderDTO.CreateOrderDTOBuilder(productId=" + this.productId + ", quantity=" + this.quantity + ", amount=" + this.amount + ")";
        }
    }
}

总结

🎉实际上,Lombok主要作用是提高代码的简洁,减少代码量,本篇文章主要是概述了Lombok的常用注解的作用,以及同时使用@Data@Builder注解时需要注意的点,我是:👨‍🎓austin流川枫,如果文章对你有所帮助,欢迎点赞👍+关注✔+收藏❤,我们下期见!