這周的某個晚上,同事喊我過去看個問題,大概是這樣的:爲了知足新的業務需求,對於A、B兩種不一樣的內容,在頁面呈現上必須區分出兩套規則,一套是用戶能夠進行修改和刪除的,一套是用戶只能查看的。java
很容易想到一種作法就是:VO(View Object) 新增 Boolean 字段,對於 A、B 兩種內容,組裝 VO 的時候 A 的該字段設爲 false,B 的該字段設爲 true,經過 MVC 的 model 和 view 交互時對它做個判斷,頁面的區分渲染就能夠實現了。spring
可是,在測試的時候,他發現一個奇怪的現象,就是這個新增的字段值居然是 null,以至於根本沒法做判斷了。咱們先看下 VO 的代碼:bash
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CommonVo {
private Long id;
private Integer status;
@Builder.Default private Boolean readOnly = false; // 是否只讀
}
複製代碼
能夠看到,在 CommonVO 中新增了名爲 readOnly 的字段,並經過 lombok 註解 @Builder.Default 給它設置默認值爲 false,並且這個 VO 類上也加了 lombok 註解,能夠說包羅萬象,乍一看沒有任何問題,可爲何不行呢?maven
咱們再看看組裝 VO 的時候,是怎麼實例化對象的:函數
CommonVO vo = new CommonVO();
複製代碼
哦,原來是用這種傳統的實例化方式啊,那 new 一個無參構造函數,默認值就丟失了?測試
咱們直接編譯一下 java 文件,看看 CommonVO 的 class 文件裏面的無參構造函數是怎樣的:ui
public CommonVo() {
}
複製代碼
無參構造函數體內居然沒有 this.readOnly = false; 這一行代碼,那顯而易見地,默認值根本就不會生效嘛。this
而後再往下看,確定是有一個 Builder 的構建器,沒錯在這裏。爲了劃重點,我把 class 內容拷貝成文本後加了三個註解,代碼以下:編碼
public static class CommonVOBuilder {
private Long id;
private Integer status;
private boolean readOnly$set; // 重點關注①
private Boolean readOnly;
CommonVOBuilder() {
}
public CommonVO.CommonVOBuilder id(final Long id) {
this.id = id;
return this;
}
public CommonVO.CommonVOBuilder status(final Integer status) {
this.status = status;
return this;
}
public CommonVO.CommonVOBuilder readOnly(final Boolean readOnly) {
this.readOnly = readOnly;
this.readOnly$set = true; // 重點關注②
return this;
}
// 重點關注③
public CommonVO build() {
return new CommonVO(this.id, this.status, this.readOnly$set ? this.readOnly : CommonVO.$default$readOnly());
}
public String toString() {
return "CommonVO.CommonVOBuilder(id=" + this.id + ", status=" + this.status + ", readOnly=" + this.readOnly + ")";
}
}
複製代碼
能夠看到,經過 lombok 編譯後生成的 CommonVOBuilder 類會多出一個 readOnly$set
字段,這個字段的做用就是用來判斷是否設置成默認值。譬如,在對象實例化的時候,若是設置了 readOnly 的值爲 true,那麼readOnly$set
就會被設爲 true,調用 build() 方法以後,就會有一個三目運算符運算來決定它應該爲 true 而不是默認值 false。這裏的 CommonVO.$default$readOnly()
方法體內就一行代碼,返回默認值:spa
private static Boolean $default$readOnly() {
return false; // 這個false就是定義readOnly設置的默認值
}
複製代碼
若是咱們從 POJO 的定義加上 lombok 註解,到對象實例化都用 lombok 的同一套風格來行事,確定就不會出這岔子。
那就直接把
CommonVO vo = new CommonVO();
複製代碼
改成
CommonVO vo = CommonVO().builder().build();
複製代碼
嗯,很是好!這是最規範的寫法,lombok 官網也推薦。
可是現實狀況是:因爲歷史緣由,項目中好多處都是直接 new 的形式來建立的,若是都按這種方式改,一是怕改漏了,二是怕改出問題。
有沒有一種折中的辦法,默認值不管是經過 new 方式仍是 Builder().build() 方式都能正常使用呢?
好吧,既然要這種騷操做,那就再來一波探索。
首先一個問題就是:爲何 lombok 的無參構造函數沒有幫咱們設置默認值?
我看了下項目的 pom.xml 裏面 lombok 的 dependency 是這樣的:
沒指定 version,再往父依賴找:
原來依賴的是 spring boot,在這個 pox 文件往上翻找查到具體版本:
而後我搜索了一下 maven 倉庫,目前最高的是 1.18.8,抱着嘗試的心態直接指定 lombok 的 version 爲最新版,編譯:
package com.example.demo.mock;
import com.example.demo.vo.CommonVO;
/**
* @author Jessehuang
*/
public class LombokDefaultValTest {
public static void main(String[] args) {
CommonVO vo = new CommonVO();
System.out.println(vo);
CommonVO vo2 = CommonVO.builder().build();
System.out.println(vo2);
}
}
複製代碼
結果:
CommonVO(id=null, status=null, readOnly=false)
CommonVO(id=null, status=null, readOnly=false)
複製代碼
OK,可行!而後出於好奇心,我想知道究竟是哪一個版本開始支持的。多嘗試了幾個,發現 1.18.2 這個版本修正了這個問題,若是不信你能夠把 lombok 的依賴指定爲 1.18.2,再編譯看看 class 文件就知道了。
另外,lombok 的 GitHub 的 issues 在2017年就有人提出這個問題,一年以後才得以修正。
若是不給 lombok 指定版本,仍是依賴 spring boot 幫你指定,那必須把 spring boot 升級到 v2.1.0.M2 版本及以上才行,你能夠在 spring boot 的 GitHub Releases 發版流水線的 Dependency upgrades 看到這個升級。
總而言之,咱們經過升級 lombok 版本的方式解決了默認值爲 null 的問題。
其實 lombok 在幫咱們減小 POJO 冗餘編碼的同時,也給咱們帶來了一些困擾。好比首字母小寫第二個字母大寫的命名方式就會形成 Jackson 失敗問題、低版本默認值經過 new 方式初始化會爲 null 的問題。
建議在編碼的時候,不要交叉着使用上面兩種實例化方式,這個必需要在團隊中達成共識。