Java中的@Builder注解问题详解

 更新时间:2023年10月02日 10:10:58   作者:Archie_java  
这篇文章主要介绍了Java中的@Builder注解详解,@Builder 注解的其中一个大坑会导致默认值失效,这是使用此注解出现的一个问题,总的来说,不推荐再使用 @Builder 注解,接下来讲重点介绍其原因和替代方案,需要的朋友可以参考下

一、前言

 @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" rel="external nofollow" >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 的行为。

到此这篇关于Java中的@Builder注解问题详解的文章就介绍到这了,更多相关Java的@Builder内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 在eclipse中使用SVN的方法(图文)

    在eclipse中使用SVN的方法(图文)

    这篇文章主要介绍了在eclipse中使用SVN的方法(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • java实现字符串转String数组的方法示例

    java实现字符串转String数组的方法示例

    这篇文章主要介绍了java实现字符串转String数组的方法,涉及java字符串的遍历、分割、转换等相关操作技巧,需要的朋友可以参考下
    2017-10-10
  • springboot之Jpa通用接口及公共方法使用示例

    springboot之Jpa通用接口及公共方法使用示例

    这篇文章主要为大家介绍了springboot 之Jpa通用接口及公共方法使用示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • java dom4j解析xml用到的几个方法

    java dom4j解析xml用到的几个方法

    这篇文章主要介绍了java dom4j解析xml用到的几个方法,有需要的朋友可以参考一下
    2013-12-12
  • Java实现滑动验证码的示例代码

    Java实现滑动验证码的示例代码

    这篇文章主要介绍了Java实现滑动验证码的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • 详解Java中super的几种用法并与this的区别

    详解Java中super的几种用法并与this的区别

    这篇文章主要介绍了Java中super的几种用法并与this的区别,有需要的朋友可以参考一下
    2013-12-12
  • Java指令重排序在多线程环境下的处理方法

    Java指令重排序在多线程环境下的处理方法

    指令重排在单线程环境下有利于提高程序的执行效率,不会对程序产生负面影响,本文对多线程指令重排问题进行复原,并针对指令重排给出相应的解决方案,需要的朋友参考下吧
    2022-04-04
  • 浅析Java多线程同步synchronized

    浅析Java多线程同步synchronized

    本篇文章给大家详细分析了Java多线程同步synchronized的相关知识点,需要的读者们可以参考学习下。
    2018-02-02
  • Springboot内置tomcat配置虚拟路径过程解析

    Springboot内置tomcat配置虚拟路径过程解析

    这篇文章主要介绍了Springboot内置tomcat配置虚拟路径过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Java中两种抛出异常的方式示例详解

    Java中两种抛出异常的方式示例详解

    在Java中有两种抛出异常的方式,一种是throw,直接抛出异常,另一种是throws,间接抛出异常,本文给大家详细说明java中两种抛出异常的方式,感兴趣的朋友一起看看吧
    2023-08-08

最新评论