不要再使用 @Builder 注解了!有深坑呀!

news2024/11/26 19:30:47

图片

曾经,我在《千万不要再随便使用 lombok 的 @Builder 了!》 一文中提到 @Builder 注解的其中一个大坑会导致默认值失效!

最近阅读了 《Oh !! Stop using @Builder》 发现 @Builder 的问题还不止一个,@Builder 会让人误以为是遵循构建器模式,实则不然,后面会介绍。

总的来说,不推荐再使用 @Builder 注解,接下来讲重点介绍其原因和替代方案。

二、场景复现

2.1 如果不使用 @Builder

类定义:

package io.gitrebase.demo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class APIResponse<T> {

    private T payload;

    private Status status;

}

使用示例:

package io.gitrebase.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(assignableTypes = io.gitrebase.demo.RestApplication.class)
public class ApplicationExceptionHandler {

    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public APIResponse handleException(Exception exception) {
        log.error("Unhandled Exception", exception);
        Status status = new Status();
        status.setResponseCode("RESPONSE_CODE_IDENTIFIER");
        status.setDescription("Bla Bla Bla");
        APIResponse response = new APIResponse();
        response.setStatus(status);
        return response;
    }

}
2.2 使用 @Builder

类定义:

package io.gitrebase.demo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class APIResponse<T> {

    private T payload;

    private Status status;

}

使用示例:

package io.gitrebase.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(basePackageClasses = io.gitrebase.demo.RestApplication.class)
public class ApplicationExceptionHandler {

    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public APIResponse handleException(Exception exception) {
        log.error("Unhandled Exception", exception);
        return APIResponse.builder().status(Status.builder()
                .responseCode("RESPONSE_CODE_IDENTIFIER")
                .description("Bla Bla Bla")
                .build())
                .build();
    }

}

三、为什么不推荐使用 @Builder?

@Builder 会生成一个不完美的构建器,它不能区分哪些参数是必须的,哪些是可选的。这可能会导致构建对象时出现错误或不一致的情况。

很多人习惯于将 @Builder 和 @Data 一起使用使用会生成一个可变的构建器,它有 setter 方法可以修改构建器的状态。这违反了构建器模式的原则,即构建器应该是不可变的,一旦创建就不能修改。

@Builder 会生成一个具体类型的构建器,它不能适应不同类型的参数。这限制了构建器模式的优势,即可以根据不同的抽象类型创建不同风格的对象。

@Builder 的使用场景很有限,它只适合那些有很多参数且大部分是可选的对象。对于那些只想实现一个流式风格的对象创建,@Builder 并不是一个好的选择。

四、替代方案

4.1 首推:@Accessor

类的定义:

package io.gitrebase.demo;

import lombok.Data;
import lombok.experimental.Accessors;


@Data
@Accessors(chain = true)
public class APIResponse<T> {

    private T payload;

    private Status status;

}

编译后的类:

package io.gitrebase.demo;

import lombok.experimental.Accessors;

@Accessors(chain = true)
public class APIResponse<T> {

    private T payload;

    private Status status;

    public T getPayload() {
        return this.payload;
    }

    public APIResponse<T> setPayload(T payload) {
        this.payload = payload;
        return this;
    }

    public Status getStatus() {
        return this.status;
    }

    public APIResponse<T> setStatus(Status status) {
        this.status = status;
        return this;
    }
}

使用示例:

package io.gitrebase.demo;

import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(basePackageClasses = io.gitrebase.demo.RestApplication.class)
public class ApplicationExceptionHandler {

    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public APIResponse handleException(Exception exception) {
        log.error("Unhandled Exception", exception);
        var status = new Status().setResponseCode("RESPONSE_CODE_IDENTIFIER").setDescription("Bla Bla Bla");
        return new APIResponse().setStatus(status);
    }

}

此外,该注解还支持一些高级方法:

/**
 * A container for settings for the generation of getters and setters.
 * <p>
 * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Accessors">the project lombok features page for &#64;Accessors</a>.
 * <p>
 * Using this annotation does nothing by itself; an annotation that makes lombok generate getters and setters,
 * such as {@link lombok.Setter} or {@link lombok.Data} is also required.
 */
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Accessors {
 /**
  * If true, accessors will be named after the field and not include a {@code get} or {@code set}
  * prefix. If true and {@code chain} is omitted, {@code chain} defaults to {@code true}.
  * <strong>default: false</strong>
  * 
  * @return Whether or not to make fluent methods (named {@code fieldName()}, not for example {@code setFieldName}).
  */
 boolean fluent() default false;
 
 /**
  * If true, setters return {@code this} instead of {@code void}.
  * <strong>default: false</strong>, unless {@code fluent=true}, then <strong>default: true</strong>
  * 
  * @return Whether or not setters should return themselves (chaining) or {@code void} (no chaining).
  */
 boolean chain() default false;
 
 /**
  * If present, only fields with any of the stated prefixes are given the getter/setter treatment.
  * Note that a prefix only counts if the next character is NOT a lowercase character or the last
  * letter of the prefix is not a letter (for instance an underscore). If multiple fields
  * all turn into the same name when the prefix is stripped, an error will be generated.
  * 
  * @return If you are in the habit of prefixing your fields (for example, you name them {@code fFieldName}, specify such prefixes here).
  */
 String[] prefix() default {};
}

另外如果一个类有些参数必传,有些参数选传,可以将必传参数定义到构造方法上,非必传参数采用 @Accessor 方式链式设置。

// 导入 lombok 注解
import lombok.Data;
import lombok.experimental.Accessors;

// 定义 Person 类
@Getter // 自动生成 getter 方法
@Accessors(chain = true) // 开启链式调用
public class Person {
    // 定义必传的属性
    private String name; // 姓名
    private int id; // 编号

    // 定义选填的属性
    private int age; // 年龄
    private String address; // 地址

    // 定义构造函数,接收必传的参数
    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        // 创建一个 Person 对象,传入必要的参数,通过链式调用,设置选填的属性
        Person person = new Person("张三", 1001).setAge(25).setAddress("北京市");

        // 打印 Person 对象的信息
        System.out.println(person);
    }
}
4.2 手动模拟 @Accessor

由于 @Accessor 在 lombok.experimental包下,有极个非常谨慎的人会担心未来不稳定,未来可能被移除。

其实,在我看来这个担心有些多余,目前这个注解比 @Builder 更适合使用,而且一个成熟的工具类库不会轻易移除一个功能,而且及时移除了这个功能编译期就可以感知到,替换起来也很容易。

如果真的担心不稳定或者不想依赖 lombok,那么自己在默认生成的 Setter 方法上改造一下即可。

五、启发

大多数同学使用 lombok 注解都不会主动看源码,了解有哪些高级配置。建议工作之余稍微花点时间去看一下源码。

大家在使用 lombok 注解时,一定要在脑海中能够准确“编译” 出背后的代码。如果你没有这个能力,早晚会遇到坑。如果你没有这个能力,那么多去看编译后的类,熟能生巧。

并不是大家都在用的都是对的,使用某些功能时需要主动思考是否正确,哪怕是正确的是否是最佳的。@Builder 注解的确和构建器设计模式有些背离,很多时候我们需要的是@Accessor 的行为。

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1578407.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java的封装

在Java中&#xff0c;封装是面向对象编程中的一种重要概念&#xff0c;它指的是将数据和方法打包在一个单一的单位&#xff08;类&#xff09;中&#xff0c;并对外部隐藏对象的内部细节。封装通过将类的成员变量声明为私有的&#xff0c;并提供公共的方法来访问和修改这些变量…

数据库关系模式三元及以上分解无损连接判断(表格法)

例题 1.首先构造初始表&#xff0c;如下表所示。 A B C D E ABC a1 a2 a3 b14 b15 CD b21 b22 a3 a4 b15 DE b31 b32 b33 a4 a5 2.遍历函数依赖&#xff0c;对AB→C&#xff0c;因各元组的第一、二列没有相同的分量&#xff0c;所以表不改变。 3.由C→D…

【日常记录】【JS】getComputedStyle 获取DOM的最终样式值

文章目录 1、介绍2、getComputedStyle3、链接 1、介绍 Window.getComputedStyle()方法返回一个对象&#xff0c;该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有 CSS 属性的值。私有的 CSS 属性值可以通过对象提供的 API 或通过简单地使用 CSS 属性…

指针的深入理解(六)

指针的深入理解&#xff08;六&#xff09; 个人主页&#xff1a;大白的编程日记 感谢遇见&#xff0c;我们一起学习进步&#xff01; 文章目录 指针的深入理解&#xff08;六&#xff09;前言一. sizeof和strlen1.1sizeof1.2strlen1.3sizeof和strlen对比 二.数组名和指针加减…

javaWeb物流信息网的设计与实现

摘要 本文讲述了基于JSP物流信息网的设计与实现。该系统使用java语言开发&#xff0c;使系统具有更好的平台性和可扩展性。 该系统实现了用户登录、注册、查询快递信息、快递公司注册成为合作伙伴以及系统管理员对信息进行管理等功能。系统的主要界面会将所有的服务排列好&…

Typora段内公式

文件——>偏好设置——>Markdown——>内联公式 $$ 输入公式 $$

【grpc】二、grpc入门,基本使用方法

上篇介绍了使用protobuf生成grpc相关代码&#xff0c;并实现了服务端方法&#xff0c;本篇介绍下具体的使用。 一、服务端 // server.gopackage mainimport ("google.golang.org/grpc""grpcDemo/calc""net" )func startServer(addr string) {//…

【C语言】函数相关选择题

前言 关于函数相关的选择题。 题目一&#xff1a; C语言规定&#xff0c;在一个源程序中&#xff0c;main函数的位置&#xff08; &#xff09; A .必须在最开始 B .必须在库函数的后面 C .可以任意 D .必须在最后 题解&#xff1a;选择C。 main函数为C语言中整个工程的程序入…

快速理解vim编辑器和软硬连接

刚开始学习linux基础命令时最难搞懂的应该就是vi和ln命令了&#x1f62d;&#xff0c;写这篇文章主要想和大家分享一下我对这两个命令的理解&#xff0c;希望能更快的帮助到那些还没搞懂的同学&#x1f61c; 首先我们要知道vim编辑器主要分为三个工作模式&#xff0c;分别是图上…

python中的split()用法

在Python中&#xff0c;split() 是一个字符串方法&#xff0c;用于将字符串按照指定的分隔符分割成一个列表。如果没有提供分隔符&#xff0c;那么它会默认按照任何空白字符&#xff08;如空格、换行符、制表符等&#xff09;进行分割。 这里是 split() 方法的一些基本用法&am…

K8s学习九(配置与存储_存储)

存储管理 Volumes HostPath 将节点上的文件或目录挂载到 Pod 上&#xff0c;此时该目录会变成持久化存储目录&#xff0c;即使 Pod 被删除后重启&#xff0c;也可以重新加载到该目录&#xff0c;该目录下的文件不会丢失 效果就是容器里的数据和主机里的数据进行共享 配置文…

Hadoop安装部署-SecondaryNameNode备份版

Hadoop分布式文件系统支持NameNode节点的高可用性&#xff0c;本文主要描述Secondary NameNode数据备份版本的安装部署。 如上所示&#xff0c;NameNode主节点同步数据到NameNode备份节点&#xff0c;当NameNode主节点发生故障变得不可用时&#xff0c; NameNode主节点重新启动…

【多线程】多线程下使用集合类

一.Java中线程安全的集合类 Java中提供了多种线程安全的集合类&#xff0c;它们可以在多线程环境下安全地被多个线程同时访问而无需外部同步。以下是一些常见的线程安全集合类&#xff1a; Vector: 类似于 ArrayList 的动态数组&#xff0c;但是所有方法都是同步的&#xff0…

ThinkPhp8 框架使用 mysql find_in_set 函数

前言: 使用mysql 存储一些标签时 会使用逗号拼接的存储方法 比如 1,2,3,11 一般情况下 查询 1 可能会用到 like %1% 但这样查询的不够准确 因为11也会被查询到 如果每次都多一个逗号 1,2,3,11, 查询时 like %1,% 这样存储有点不太符合程序设计 解决方案 ----------- 官网…

Linux 5.10 Pstore 功能测试

目录 简介环境配置内核配置参考备注 简介 Pstore(Persistent store support)是用于系统发生oops或panic时&#xff0c;自动保存内核log buffer中的日志。随着功能不断完善&#xff0c;Duo S使用Linux 5.10已经支持保存console日志、ftrace消息和用户空间日志的收集&#xff0c…

【Shell语言学堂】Shell 脚本练习1

Shell 脚本练习 shell语言实战 Shell 脚本练习&#x1f4a7;CSDN划过手的泪滴t现有一个脚本可传入n个参数&#xff0c;要求在脚本中实现在终端输出第n个参数之前的所有参数(不包含第n个参数)编写一个计算bmi体质指数的脚本&#xff0c;该脚本需要用户输入身高和体重信息&#x…

登录信息失效后多次请求提示合并成一次

在通常的业务场景中经常会出现进入页面之后一次性发送好多个请求,如果登录信息失效,那就会出现很多提示 类似这种多个提示的,看起来不美观,希望改成可以把在短时间内出现相同的错误信息,只提示一次,其他的就不提示了 实现思路 通常业务中每一个请求的code都是有具体的意思,可以…

虚拟机VMware启动虚拟机刚启动有网之后没网

虚拟机VMware启动虚拟机刚启动有网之后没网 害&#xff0c;感觉这种调试的事情是真的浪费时间 如题&#xff0c;对于这种情况&#xff0c;一句话&#xff0c;就是你本地的DHCP虚拟机服务以及NAT网络服务没启动 本机windowR,输入services.msc 进入服务 然后 喏&#xff0c;…

【MATLAB源码-第182期】基于matlab的QPSK调制解调系统频偏估计及补偿算法仿真,对比补偿前后的星座图误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在通信系统中&#xff0c;频率偏移是一种常见的问题&#xff0c;它会导致接收到的信号频率与发送信号的频率不完全匹配&#xff0c;进而影响通信质量。在调制技术中&#xff0c;QPSK&#xff08;Quadrature Phase Shift Keyi…

LowCodeEngine基础教程

一、技术介绍 1、应用场景 LowCodeEngine支持一下四种通用场景&#xff1a;中后台页面、移动端页面、流程、可视化报表。 2、低代码的特点 3、低代码在阿里巴巴实践 4、开发痛点 中后台页面中&#xff0c;对于页面的视觉冲击、视觉炫酷要求并不是很敏感&#xff0c;更多的是…