Don't use the @Builder annotation anymore!

Author : Mingming Ruyue senior, CSDN blog expert, senior Java engineer of Ant Group, author of "Performance Optimization Methodology", "Unlocking Big Factory Thinking: Analysis of "Alibaba Java Development Manual", "Re-learning Classics: Exclusive Analysis of "EffectiveJava"" Columnist.

Recommended popular articles :

insert image description here

I. Introduction

Once, I was in "Don't use lombok's @Builder casually anymore! " In the article, one of the big pits mentioned in the @Builder annotation will cause the default value to fail!

I recently read "Oh !! Stop using @Builder" and found @Buildermore than one problem, @Builderwhich will make people mistakenly think that it follows the builder pattern, but it is not the case, which will be introduced later.

In general, annotations are no longer recommended @Builder, and the next section will focus on the reasons and alternatives.

2. Scene reproduction

2.1 If you don't use @Builder

class definition:

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;

}

Example usage:

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 use@Builder

class definition:

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;

}

Example usage:

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();
    }

}

3. Why is it not recommended **@Builder**?

  • @Builder produces an imperfect builder that cannot distinguish which parameters are required and which are optional. This can lead to errors or inconsistencies when building objects.
  • Many people are accustomed to using @Builder and @Data together to generate a mutable builder that has a setter method to modify the state of the builder. This violates the principle of the builder pattern that builders should be immutable and cannot be modified once created.
  • @Builder generates a concrete type of builder that cannot accommodate arguments of different types . This limits the advantage of the builder pattern, which can create different styles of objects based on different abstract types.
  • The usage scenarios of @Builder are very limited, it is only suitable for objects that have many parameters and most of them are optional . For those who just want to achieve a fluent style of object creation, @Builder is not a good choice.

4. Alternatives

4.1 Top recommendation:@Accessor

Class definition:

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;

}

Compiled class:

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;
    }
}

Example usage:

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);
    }

}

In addition, this annotation supports some advanced methods:

/**
 * 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 {
    
    };
}

In addition, if some parameters of a class must be passed and some parameters are optional, you can define the required parameters to the construction method, and use the chain @Accessorsetting method for non-required parameters.

// 导入 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 Manual simulation@Accessor

Because it @Accessoris lombok.experimentalunder the package, there are extremely cautious people who worry about future instability and may be removed in the future.
In fact, in my opinion, this worry is a bit redundant. At present, this annotation is @Buildermore suitable for use, and a mature tool library will not easily remove a function, and the removal of this function in time can be perceived at compile time and replaced. It's also easy.
If you are really worried about instability or don't want to rely on lombok, then you can modify the default generated Setter method yourself.

5. Inspiration

Most students will not take the initiative to look at the source code to learn about advanced configurations when using lombok annotations. It is recommended to take a little time to look at the source code after work.
When you use lombok annotations, you must be able to accurately "compile" the code behind it in your mind. If you don't have this ability, you will encounter pitfalls sooner or later. If you don't have this ability, then look at the compiled class more, practice makes perfect.

Not everyone is using the right ones. When using certain functions, you need to actively think about whether it is correct, even if it is correct and whether it is the best. @BuilderAnnotations do deviate from the builder design pattern, and many times what we need is @Accessorbehavior.

おすすめ

転載: blog.csdn.net/w605283073/article/details/132417856