六角鼠年鐵人賽Week 11 - Spring Boot - Lombok 省時省力好幫手

文章推薦指數: 80 %
投票人數:10人

六角鼠年鐵人賽Week 11 - Spring Boot - Lombok 省時省力好幫手. 大家好,我是“為了拿到金角獎盃而努力著” 的文毅青年- Kai. 賦得古原草送别白居易.       Published LinkedwithGitHub Like Bookmark Subscribe #六角鼠年鐵人賽Week11-SpringBoot-Lombok省時省力好幫手 ==大家好,我是"為了拿到金角獎盃而努力著"的文毅青年-Kai== ##賦得古原草送别白居易 :::info 離離原上草,一歲一枯榮。

野火燒不盡,春風吹又生。

遠芳侵古道,晴翠接荒城。

又送王孫去,萋萋滿別情。

::: ##主題 :::info 說到CleanClear是否有想起早年露得清的廣告台詞呢? 沒錯!今天要介紹的**Lombok**就是具備了這樣特性的套件。

::: 是否寫煩了每一個Bean的Get()、Set()、toString()方法? 是否厭倦每次處理POJO物件就要用DB套件去Gen出相關Class? 那你一定得使用看看**Lombok**! **Lombok**便是專門幫助Java開發人員減少寫這些重複程式碼的好套件! ##Lombok介紹 **Lombok**是一個發展已久的OpenSource,於2011年被開發出來支援當時的JDK1.6版本,到現在依然維持著一年3~4個patch的更新。

**Lombok**致力於代替開發者處理許多重複性質的程式碼撰寫,除了上述提到的部分外,還有如Resourcesrelease、Log等幾個實用的功能也在近幾年的patch中實裝了。

其實現的原理如同一般Spring的@annotation一樣,是透過編譯處理時,若有對應到的註解的Class,註解編譯器會自動去對應項目中的註解文件,並自動編譯產生對應Class中物件的方法。

所以Lombok套件在使用時機,通常只會在Compile的時候,在Compile完成後,便不再需要Lombok套件,因此在build.gradle裡面的設定,就可以調整為只在Compile時候才加入這個套件的方式。

##Lombok提供的功能 |方法名稱|主要功用|包含方法|附註| |---|---|---|---| |@AllArgsConstructor|產生全類別參數的建構子||| |@NoArgsConstructor|產生無類別參數的建構子||對於@NonNull的參數不產出判斷式,因此可能會因無資料造成錯誤| |@RequiredArgsConstructor|針對@NonNull和未初始化的final參數產生類別參數的建構子||使用上需與@AllArgsConstructor或@NoArgsConstructor搭配使用| |@Builder|提供新方式去建構類實體||[詳細可看官網介紹](https://projectlombok.org/features/Builder)| |@Builder.Default|當使用@Builder在類別時,可用此方式設定給類別參數|| |@Singular|與@Builder合用,詳細可參考Builder官網文件|| |@Cleanup|指定類別參數,當程式退出當前執行域的時候進行清除資源動作|| |@Data|產生DTO類別方法|@ToString@EqualsAndHashCode@Getter[非final欄位]@Setter@RequiredArgsConstructor| |@EqualsAndHashCode|產生equals和hashCode方法|| |@Getter|產生類別參數的GET方法|| |@Setter|產生類別參數的SET方法|| |@NonNull|產生類別參數的Null判斷式||會拋出NullPointException| |@SneakyThrows|產生略過拋出錯誤的程式|| |@Synchronized|將類別方法或參數宣告成同步|| |@ToString|產生類別參數的toString方法||可用@ToString(exclude="類別參數名稱")進行排除;可用@ToString(callSuper=true,includeFieldNames=true)操作父類別的參數| |@log|根據不同註解產出不同LOG實體,defaultFieldName:log||支援類型:@CommonsLog、@Log4j、@Log4j2、@Slf4j、@Log、@Flogger、@JBossLog、@XSlf4j |val|使用在類別參數前,可將參數宣告為final|| |@Value|使用在類別前,可將類別宣告為final,並指產出類別參數的GET方法|| 特別注意到@SneakyThrows是將checkedexception看做uncheckedexception,程式會不進行處理直接略過,好處是在很多需要寫catch的部分會減少很多程式碼,壞處就是真正發生問題時後非常難追,使用上要注意,盡量替用在那些基本不會出錯的程式中。

##實例 我將會使用在這篇文章[六角鼠年鐵人賽Week10-SpringBoot-BuildfirstAPI](/cWafHYsTT6yRuqedj0CluQ)所做的專案當作基底,直接增加範例API進來。

這次的專案程式架構將會長的如下圖: ![](https://i.imgur.com/9gUqZBH.png) ##build.gradle增加套件設定 打開**build.gradle**檔案後,找到**dependencies**的部分,在裏頭增加下列兩行程式碼。

設定lombok只能在Compile時後執行,並設定@annotation的處理,這部分還需要搭配後續的動作才能設定正確。

``` compileOnly'org.projectlombok:lombok:1.18.4' annotationProcessor'org.projectlombok:lombok:1.18.4' ``` :::spoiler**完整的build.gradle內容** ``` plugins{ id'java' id'org.springframework.boot'version'2.2.0.RELEASE' id'io.spring.dependency-management'version'1.0.8.RELEASE' } group'kai.com' version'1.0-SNAPSHOT' sourceCompatibility=1.8 repositories{ mavenCentral() } dependencies{ implementation'org.springframework.boot:spring-boot-starter-web' testImplementation('org.springframework.boot:spring-boot-starter-test'){ excludegroup:'org.junit.vintage',module:'junit-vintage-engine' } compileOnly'org.projectlombok:lombok:1.18.4' annotationProcessor'org.projectlombok:lombok:1.18.4' } test{ useJUnitPlatform() } applyplugin:'application' mainClassName='kai.com.springbootApplicationStarter' ``` ::: ##EnableAnnotationProcessor 打開Settings的窗格,方法如下: 1.快捷鍵:Ctrl+Alt+S 2.找到右上角File>Settings 進入**Build,Execution,Deployment**>**Compiler**>**AnnotationProcessors**中 找到**Enableannotationprocessing**並打勾,如下圖所示: ![](https://i.imgur.com/IjOzYLI.png) ##創建多個測試用的Bean :::spoiler**BeanFor@AllArgsConstructor@NonNull@Getter@Setter** ``` packagekai.com.lombok.bean; importlombok.*; @Getter @Setter @AllArgsConstructor publicclasslombokAllArgsBean{ privateStringusername; privateStringpassword; @NonNull privateIntegerid; } /* youwillget publiclombokAllArgsBean(intid,Stringusername,Stringpassword){ if(id==null) thrownewNullPointerException("id"); this.id=id; this.username=username; this.password=password; } */ ``` ::: :::spoiler**BeanFor@NoArgsConstructor** ``` packagekai.com.lombok.bean; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.NonNull; importlombok.Setter; @Getter @Setter @NoArgsConstructor publicclasslombokNoArgsBean{ privateStringusername; privateStringpassword; @NonNull privateIntegerid; } /* youwillget publiclombokNoArgsBean(){} */ ``` ::: :::spoiler**BeanFor@RequiredArgsConstructor** ``` packagekai.com.lombok.bean; importlombok.*; @Getter @Setter @NoArgsConstructor @RequiredArgsConstructor publicclasslombokRequireArgsBean{ privateStringusername; privateStringpassword; @NonNull privateIntegerid; } /* youwillget privatelombokRequireArgsBean(intid){ if(id==null)thrownewNullPointerException("id"); this.id=id; } publicstaticlombokRequireArgsBeanof(intid){ returnnewlombokRequireArgsBean(id); } */ ``` ::: :::spoiler**BeanFor@Builder** ``` packagekai.com.lombok.bean; importlombok.*; @Setter @Getter @Builder @NoArgsConstructor @AllArgsConstructor publicclasslombokBuilderBean{ privateIntegerid; privateStringusername; privateStringpassword; } ``` ::: :::spoiler**BeanFor@EqualsAndHashCode** ``` packagekai.com.lombok.bean; importlombok.*; @Getter @Setter @AllArgsConstructor @EqualsAndHashCode publicclasslombokEqualsAndHashCodeBean{ privateStringusername; privateStringpassword; @NonNull privateIntegerid; } ``` ::: :::spoiler**BeanFor@ToString** ``` packagekai.com.lombok.bean; importlombok.*; @Getter @Setter @AllArgsConstructor @ToString publicclasslombokToStringBean{ privateStringusername; privateStringpassword; @NonNull privateIntegerid; } ``` ::: :::spoiler**BeanFor@SneakyThrows** ``` packagekai.com.lombok.bean; importlombok.*; importjava.io.IOException; @Getter @Setter @AllArgsConstructor publicclasslombokSneakyThrowsBean{ privateStringusername; privateStringpassword; @NonNull privateIntegerid; publicvoidthrowCheckedException()throwsIOException{ thrownewIOException("Checked,DeclaresandThrowsIOException."); } publicvoidhandleCheckedException()throwsIOException{ try{ thrownewIOException("Checked,DeclaresandHandlesIOException."); }catch(IOExceptione){ System.out.println(e.toString()); throwe;//forprintonAPIresponse } } publicvoidthrowUnCheckedException(){ thrownewRuntimeException("UncheckedRuntimeException."); } @SneakyThrows publicStringsneakyThrowsCheckedException(){ thrownewIOException("Sneaky,Checked,DeclaresandThrowsIOException."); } } ``` ::: :::spoiler**BeanFor@log** ``` packagekai.com.lombok.bean; importlombok.AllArgsConstructor; importlombok.Getter; importlombok.NonNull; importlombok.Setter; importlombok.extern.apachecommons.CommonsLog; importlombok.extern.flogger.Flogger; importlombok.extern.java.Log; importlombok.extern.jbosslog.JBossLog; importlombok.extern.log4j.Log4j; importlombok.extern.log4j.Log4j2; importlombok.extern.slf4j.Slf4j; importlombok.extern.slf4j.XSlf4j; @Getter @Setter @AllArgsConstructor @CommonsLog /* @Log4j @Log4j2 @Slf4j @Log @Flogger @JBossLog @XSlf4j */ publicclasslombokLogBean{ privateStringusername; privateStringpassword; @NonNull privateIntegerid; publicvoiddoGenerateLog(){ log.error("Somethingelseiswronghere"); } } ``` ::: ##創建測試用的Controller :::spoiler**ControllerForLombok** ``` packagekai.com.lombok.controller; importkai.com.lombok.bean.*; importorg.springframework.web.bind.annotation.*; importjava.util.HashMap; importjava.util.Map; @RestController publicclasslombokController{ @PostMapping("/lombok/NoArgs") publiclombokNoArgsBeandoPost1(@RequestBodylombokNoArgsBeanlombokB){ /*error,becausethereisanyconstructorwithparameterbecreatedbyLombok*/ //lombokNoArgsBeannewBean=newlombokNoArgsBean(lombokB.getUsername(),lombokB.getPassword(),lombokB.getId()); /*belowcanwork*/ lombokNoArgsBeannewBean=newlombokNoArgsBean(); returnnewBean; } @PostMapping("/lombok/AllArgs") publiclombokAllArgsBeandoPost2(@RequestBodylombokAllArgsBeanlombokB){ lombokAllArgsBeannewBean=newlombokAllArgsBean(lombokB.getUsername(),lombokB.getPassword(),lombokB.getId()); returnnewBean; } @PostMapping("/lombok/RequireArgs") publiclombokRequireArgsBeandoPost3(@RequestBodylombokRequireArgsBeanlombokB){ lombokRequireArgsBeannewBean=newlombokRequireArgsBean(lombokB.getId()); returnnewBean; } @GetMapping("/lombok/builder") publiclombokBuilderBeandoGet(){ lombokBuilderBeanbean=lombokBuilderBean.builder().id(1).username("Kai").password("password").build(); returnbean; } @GetMapping("/lombok/toString") @ResponseBody publicMapdoGet2(@RequestParam("id")intid,@RequestParam("username")Stringusername,@RequestParam("password")Stringpassword){ lombokToStringBeanbean=newlombokToStringBean(username,password,id); Mapmap=newHashMap(); map.put("ToString",bean.toString()); returnmap; } @GetMapping("/lombok/equalsAndHashCode") @ResponseBody publicMapdoGet3(@RequestParam("id")intid,@RequestParam("username")Stringusername,@RequestParam("password")Stringpassword){ lombokEqualsAndHashCodeBeanbean=newlombokEqualsAndHashCodeBean(username,password,id); Mapmap=newHashMap(); map.put("EqualsIDGetand1:",bean.getId()==1); map.put("EqualsusernameGetandKai:",bean.getUsername().equals("Kai")); map.put("EqualspasswordGetandtest111:",bean.getPassword().equals("test111")); map.put("HashCode:",bean.hashCode()); returnmap; } @GetMapping("/lombok/sneakyThrows") @ResponseBody publicMapdoGet4(@RequestParam("id")intid,@RequestParam("username")Stringusername,@RequestParam("password")Stringpassword){ lombokSneakyThrowsBeanbean=newlombokSneakyThrowsBean(username,password,id); Mapmap=newHashMap(); try{ bean.throwCheckedException(); }catch(Exceptione){ map.put("Case1:",e.toString()); } try{ bean.handleCheckedException(); }catch(Exceptione){ map.put("Case2:",e.toString()); } try{ bean.throwUnCheckedException(); }catch(Exceptione){ map.put("Case3:",e.toString()); } try{ bean.sneakyThrowsCheckedException(); }catch(Exceptione){ map.put("Case4:",e.toString()); } returnmap; } @GetMapping("/lombok/log") publicvoiddoGet5(@RequestParam("id")intid,@RequestParam("username")Stringusername,@RequestParam("password")Stringpassword){ lombokLogBeanbean=newlombokLogBean(username,password,id); System.out.println("RecordLogs"); bean.doGenerateLog(); return; } } ``` ::: ##測試結果與說明 ###@AllArgsConstructor@NonNull@Getter@Setter >正常的Request中,開發者可以直接使用由Lombok建立的類別參數方法 ![](https://i.imgur.com/pTW9fuW.png) >當應該不能為Null的ID為Null的狀況如下,會拋出Null錯誤並返回HttpErrorCode400的BadRequest告知 ![](https://i.imgur.com/K2btZRk.png) ###@NoArgsConstructor >由於產生的是沒有參數放入的建構子,因此在建立實體同時把值放入的狀況中就會發生錯誤 ![](https://i.imgur.com/azuqKta.png) ``` error:constructorlombokNoArgsBeaninclasslombokNoArgsBeancannotbeappliedtogiventypes; lombokNoArgsBeannewBean=newlombokNoArgsBean(lombokB.getUsername(),lombokB.getPassword(),lombokB.getId()); ^ required:noarguments found:String,String,Integer reason:actualandformalargumentlistsdifferinlength ``` ###@RequiredArgsConstructor >@RequiredArgsConstructor必須要與@AllArgsConstructor或@NoArgsConstructor其中一個搭配使用(本範例搭配@NoArgsConstructor) ![](https://i.imgur.com/nhQezD2.png) >同樣的當ID值為Null狀況,一樣會回傳錯誤訊息 ![](https://i.imgur.com/uAiqNAa.png) >@RequiredArgsConstructor(staticName="of")會創建一個名稱為of的static類別出來 ###@Builder >設置了@Builder我們便可以在實作該Class的時候以較簡約值觀的方式建立實體 >![](https://i.imgur.com/vbHclvr.png) ![](https://i.imgur.com/dvh8yDi.png) ###@ToString >協助生成toString() ![](https://i.imgur.com/bPZtxl3.png) ###@EqualsAndHashCode >協助生成equals()和hashCode() >![](https://i.imgur.com/zKCCz9W.png) ###@SneakyThrows >這一塊可能比較不好懂,需要搭配以下圖解: >方法1和方法2都是針對CheckedException做處理,差別只在於一個拋出一個在當下handle。

>方法3則是處置UnCheckedException >方法4是SneakyThrows,主要是讓開發者可以用類似處理UnCheckedException的方式,節省CheckedException的程式碼 >![](https://i.imgur.com/qjW0EUo.png) >在response中看得更清楚,方法4與方法1和方法2達到了相同的目的。

>==注意==**盡量**用在確保不會出錯的程序中,避免增加除錯上的困難。

![](https://i.imgur.com/VgkxVfj.png) ###@Log >可以使用的Log類型如下: >>@CommonsLog @Log4j @Log4j2 @Slf4j @Log @Flogger @JBossLog @XSlf4j >使用後也有正確在console上取得資訊 ![](https://i.imgur.com/1KyAMTJ.png) >至於為何沒有產出LogFile的部分?這要保留到下一次說明Log的時候再說了~而做到這邊沒有看到LogFile的朋友也不要緊張,這並不是Lombok的問題,而是Log的設定關係,後續在該章節的時候Kai這邊會好好說明。

在目前只需理解成,系統沒有任何設定告訴它要把Log除了印在Console介面外還要印在哪裡? ###@Data >對所有的類別參數建立@ToString,@EqualsAndHashCode,@Getter,針對非Final屬性的類別參數建立@Setter,加上@RequiredArgsConstructor,其產出的Class方法與上述多雷同,不多贅述,多用於DTO的處理上。

###@Cleanup >加在任何**具有close()方法的類別參數**前,Lombok會在Compile時,改寫該類別進trycatch{}中,並自動建立final的close()動作,確保資源釋放。

>特別注意,若想使用在**無close()方法的類別參數**,其必須有至少一個無參數帶入的方法,可使用類似這樣的方式處理: ==@Cleanup("dispose")org.eclipse.swt.widgets.CoolBarbar=newCoolBar(parent,0);== [下列為官方@Cleanup的範例](https://projectlombok.org/features/Cleanup) 編譯前: ``` importlombok.Cleanup; importjava.io.*; publicclassCleanupExample{ publicstaticvoidmain(String[]args)throwsIOException{ @CleanupInputStreamin=newFileInputStream(args[0]); @CleanupOutputStreamout=newFileOutputStream(args[1]); byte[]b=newbyte[10000]; while(true){ intr=in.read(b); if(r==-1)break; out.write(b,0,r); } } } ``` 編譯後: ``` importjava.io.*; publicclassCleanupExample{ publicstaticvoidmain(String[]args)throwsIOException{ InputStreamin=newFileInputStream(args[0]); try{ OutputStreamout=newFileOutputStream(args[1]); try{ byte[]b=newbyte[10000]; while(true){ intr=in.read(b); if(r==-1)break; out.write(b,0,r); } }finally{ if(out!=null){ out.close(); } } }finally{ if(in!=null){ in.close(); } } } } ``` ###@Synchronized >同Java處理Synchronized修飾子的功能,差別在於其針對靜態類別方法與非靜態類別的方法自動建立的鎖名稱不同。

>>靜態類別方法(static):$LOCK >>非靜態類別方法:$lock >當然你也可以設定屬於自己的鎖,並在設定時加入其中:@Synchronized("鎖物件名稱") ###val >設置在類別參數前,在Compile時候會被編譯加上final的修飾 ###@Value >設置在類別前,與@Data功能相反,在Compile時所有類別參數與方法都會被加上private或final的修飾 >@Value(staticConstructor="of")會額外建立一個static類別,同樣會帶有final的修飾。

###@With >添加給有使用@AllArgsConstructor的類別,會在判定目標類別參數輸入值與之前輸入不同時,自動建立一個新實體並回傳,新實體會保留原實體的其他欄位值。

>應用在建立部分參數相同;部分參數不同的狀況下非常適合使用。

##結語 :::danger 以上內容就差不多介紹完Lombok這個好用的套件了,Kai自己在練習的時候Intellij出現一堆紅色警告線,但執行Compile都可以通過,後續可能在補充如何把這線條消掉,避免誤會的狀況吧。

下一篇將介紹Log套件 [六角鼠年鐵人賽Week12-SpringBoot-一次搞懂LOG設定](/GuX8ngltT2yUWHWeb5UV-g) ::: 首頁[Kai個人技術Hackmd](/2G-RoB0QTrKzkftH2uLueA) ######tags:`SpringBoot`,`w3HexSchool` × Signin Email Password Forgotpassword or Byclickingbelow,youagreetoourtermsofservice. SigninviaFacebook SigninviaTwitter SigninviaGitHub SigninviaDropbox SigninviaGoogle NewtoHackMD?Signup



請為這篇文章評分?