當前位置: 妍妍網 > 碼農

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

2024-03-29碼農

曾經,我在【千萬不要再隨便使用 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 classAPIResponse<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 classApplicationExceptionHandler
{
@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 classAPIResponse<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(basePackage classes = io.gitrebase.demo.RestApplication. class)
public classApplicationExceptionHandler
{
@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 classAPIResponse<T{
private T payload;
private Status status;
}



編譯後的類:

package io.gitrebase.demo;
import lombok.experimental.Accessors;
@Accessors(chain = true)
public classAPIResponse<T{
private T payload;
private Status status;
public T getPayload(){
returnthis.payload;
}
public APIResponse<T> setPayload(T payload){
this.payload = payload;
returnthis;
}
public Status getStatus(){
returnthis.status;
}
public APIResponse<T> setStatus(Status status){
this.status = status;
returnthis;
}
}






使用範例:

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(basePackage classes = io.gitrebase.demo.RestApplication. class)
public classApplicationExceptionHandler
{
@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");
returnnew 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}).
*/

booleanfluent()defaultfalse;
/**
* 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).
*/

booleanchain()defaultfalse;
/**
* 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 classPerson{
// 定義必傳的內容
private String name; // 姓名
privateint id; // 編號
// 定義選填的內容
privateint age; // 年齡
private String address; // 地址
// 定義建構函式,接收必傳的參數
publicPerson(String name, int id){
this.name = name;
this.id = id;
}
}
// 使用範例
public classMain{
publicstaticvoidmain(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 的行為。

參考

千萬不要再隨便使用 lombok 的 @Builder 了!

  • https://blog.csdn.net/w605283073/article/details/130190814

  • Oh !! Stop using @Builder

  • https://medium.com/gitrebase/oh-stop-using-builder-9061a5911d8c

  • 來源: mingmingruyue.blog.csdn.net/article/details/132417856

  • >>

    END

    精品資料, 超贊福利, 免費領

    微信 掃碼 / 長按辨識 添加【 技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式 ;其中收錄了 上千道 常見面試題及答案(包含 基礎 並行 JVM MySQL Redis Spring SpringMVC SpringBoot SpringCloud 訊息佇列 等多個型別),歡迎您的使用。

    👇👇

    👇 點選" 閱讀原文 ",獲取更多資料( 持續更新中