Java中的@Builder注解問(wèn)題詳解
一、前言
@Builder 注解的其中一個(gè)大坑會(huì)導(dǎo)致默認(rèn)值失效!
@Builder 的問(wèn)題還不止一個(gè), @Builder 會(huì)讓人誤以為是遵循構(gòu)建器模式,實(shí)則不然,后面會(huì)介紹。
總的來(lái)說(shuō),不推薦再使用 @Builder 注解,接下來(lái)講重點(diǎn)介紹其原因和替代方案。
二、場(chǎng)景復(fù)現(xiàn)
2.1 如果不使用 @Builder
類(lèi)定義:
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
類(lèi)定義:
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 會(huì)生成一個(gè)不完美的構(gòu)建器,它不能區(qū)分哪些參數(shù)是必須的,哪些是可選的。這可能會(huì)導(dǎo)致構(gòu)建對(duì)象時(shí)出現(xiàn)錯(cuò)誤或不一致的情況。
- 很多人習(xí)慣于將 @Builder 和 @Data 一起使用使用會(huì)生成一個(gè)可變的構(gòu)建器,它有 setter 方法可以修改構(gòu)建器的狀態(tài)。這違反了構(gòu)建器模式的原則,即構(gòu)建器應(yīng)該是不可變的,一旦創(chuàng)建就不能修改。
- @Builder 會(huì)生成一個(gè)具體類(lèi)型的構(gòu)建器,它不能適應(yīng)不同類(lèi)型的參數(shù)。這限制了構(gòu)建器模式的優(yōu)勢(shì),即可以根據(jù)不同的抽象類(lèi)型創(chuàng)建不同風(fēng)格的對(duì)象。
- @Builder 的使用場(chǎng)景很有限,它只適合那些有很多參數(shù)且大部分是可選的對(duì)象。對(duì)于那些只想實(shí)現(xiàn)一個(gè)流式風(fēng)格的對(duì)象創(chuàng)建,@Builder 并不是一個(gè)好的選擇。
四、替代方案
4.1 首推: @Accessor
類(lèi)的定義:
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; }
編譯后的類(lèi):
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); } }
此外,該注解還支持一些高級(jí)方法:
/** * A container for settings for the generation of getters and setters. * <p> * Complete documentation is found at <a rel="external nofollow" >the project lombok features page for @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 {}; }
另外如果一個(gè)類(lèi)有些參數(shù)必傳,有些參數(shù)選傳,可以將必傳參數(shù)定義到構(gòu)造方法上,非必傳參數(shù)采用 @Accessor 方式鏈?zhǔn)皆O(shè)置。
// 導(dǎo)入 lombok 注解 import lombok.Data; import lombok.experimental.Accessors; // 定義 Person 類(lèi) @Getter // 自動(dòng)生成 getter 方法 @Accessors(chain = true) // 開(kāi)啟鏈?zhǔn)秸{(diào)用 public class Person { // 定義必傳的屬性 private String name; // 姓名 private int id; // 編號(hào) // 定義選填的屬性 private int age; // 年齡 private String address; // 地址 // 定義構(gòu)造函數(shù),接收必傳的參數(shù) public Person(String name, int id) { this.name = name; this.id = id; } } // 使用示例 public class Main { public static void main(String[] args) { // 創(chuàng)建一個(gè) Person 對(duì)象,傳入必要的參數(shù),通過(guò)鏈?zhǔn)秸{(diào)用,設(shè)置選填的屬性 Person person = new Person("張三", 1001).setAge(25).setAddress("北京市"); // 打印 Person 對(duì)象的信息 System.out.println(person); } }
4.2 手動(dòng)模擬 @Accessor
由于 @Accessor 在 lombok.experimental包下,有極個(gè)非常謹(jǐn)慎的人會(huì)擔(dān)心未來(lái)不穩(wěn)定,未來(lái)可能被移除。 其實(shí),在我看來(lái)這個(gè)擔(dān)心有些多余,目前這個(gè)注解比 @Builder 更適合使用,而且一個(gè)成熟的工具類(lèi)庫(kù)不會(huì)輕易移除一個(gè)功能,而且及時(shí)移除了這個(gè)功能編譯期就可以感知到,替換起來(lái)也很容易。 如果真的擔(dān)心不穩(wěn)定或者不想依賴(lài) lombok,那么自己在默認(rèn)生成的 Setter 方法上改造一下即可。
五、啟發(fā)
大多數(shù)同學(xué)使用 lombok 注解都不會(huì)主動(dòng)看源碼,了解有哪些高級(jí)配置。建議工作之余稍微花點(diǎn)時(shí)間去看一下源碼。 大家在使用 lombok 注解時(shí),一定要在腦海中能夠準(zhǔn)確“編譯” 出背后的代碼。如果你沒(méi)有這個(gè)能力,早晚會(huì)遇到坑。如果你沒(méi)有這個(gè)能力,那么多去看編譯后的類(lèi),熟能生巧。
并不是大家都在用的都是對(duì)的,使用某些功能時(shí)需要主動(dòng)思考是否正確,哪怕是正確的是否是最佳的。@Builder 注解的確和構(gòu)建器設(shè)計(jì)模式有些背離,很多時(shí)候我們需要的是@Accessor 的行為。
到此這篇關(guān)于Java中的@Builder注解問(wèn)題詳解的文章就介紹到這了,更多相關(guān)Java的@Builder內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot框架aop切面的execution表達(dá)式解讀
這篇文章主要介紹了SpringBoot框架aop切面的execution表達(dá)式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Java實(shí)現(xiàn)調(diào)用外部程序的示例代碼
本文主要介紹了Java實(shí)現(xiàn)調(diào)用外部程序的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Spring處理@Async導(dǎo)致的循環(huán)依賴(lài)失敗問(wèn)題的方案詳解
這篇文章主要為大家詳細(xì)介紹了SpringBoot中的@Async導(dǎo)致循環(huán)依賴(lài)失敗的原因及其解決方案,文中的示例代碼講解詳細(xì),感興趣的可以學(xué)習(xí)一下2022-07-07談?wù)凧ava中Volatile關(guān)鍵字的理解
volatile這個(gè)關(guān)鍵字可能很多朋友都聽(tīng)說(shuō)過(guò),或許也都用過(guò)。在Java 5之前,它是一個(gè)備受爭(zhēng)議的關(guān)鍵字,因?yàn)樵诔绦蛑惺褂盟鶗?huì)導(dǎo)致出人意料的結(jié)果,本文給大家介紹java中volatile關(guān)鍵字,需要的朋友參考下2016-03-03Spring的事件機(jī)制知識(shí)點(diǎn)詳解及實(shí)例分析
在本篇內(nèi)容里小編給大家分享的是一篇關(guān)于Spring的事件機(jī)制知識(shí)點(diǎn)詳解及實(shí)例分析,有需要的朋友么可以參考下。2021-12-12Springboot 使用具體化類(lèi)和配置來(lái)縮短單元測(cè)試時(shí)間
這篇文章主要介紹了Springboot 使用具體化類(lèi)和配置來(lái)縮短單元測(cè)試時(shí)間,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11