使用Lombok框架
在編寫POJO類型(包括實(shí)體類、VO、DTO等)時(shí),都有統(tǒng)一的編碼規(guī)范,例如:
由于以上操作方式非常固定,且涉及的代碼量雖然不難,但是篇幅較長(zhǎng),并且,當(dāng)類中的屬性需要修改時(shí)(包括修改原有屬性、或增加新屬性、刪除原有屬性),對(duì)應(yīng)的其它方法都需要修改(或重新生成),管理起來(lái)比較麻煩。
使用Lombok框架可以極大的簡(jiǎn)化這些操作,此框架可以通過(guò)注解的方式,在編譯期來(lái)生成Setters & Getters、`equals()`、`hashCode()`、`toString()`,甚至生成構(gòu)造方法等,所以,一旦使用此框架,開發(fā)人員就只需要在類中聲明各屬性、實(shí)現(xiàn)`Serializable`、添加Lombok指定的注解即可。
在Spring Boot中,添加Lombok依賴,可以在創(chuàng)建項(xiàng)目時(shí)勾選,也可以后期自行添加,依賴項(xiàng)的代碼為:
//依賴 org.projectlombok lombok true
完成后,在各POJO類型中,將不再需要在源代碼添加Setters & Getters、`equals()`、`hashCode()`、`toString()`這些方法,只需要在POJO類上添加`@Data`注解即可!
當(dāng)添加`@Data`注解,且刪除相關(guān)方法后,由于源代碼中沒有相關(guān)方法,則調(diào)用了相關(guān)代碼的方法可能會(huì)報(bào)錯(cuò),但是,并不影響程序運(yùn)行!
為了避免IntelliJ IDEA判斷失誤而提示了警告和錯(cuò)誤,推薦安裝Lombok插件,可參考:
小辣椒插件
【注】:無(wú)論是否安裝插件,都不影響代碼的編寫和運(yùn)行!
Slf4j日志框架
在開發(fā)實(shí)踐中,不允許使用`System.out.println()`或類似的輸出語(yǔ)句來(lái)輸出顯示關(guān)鍵數(shù)據(jù)(核心數(shù)據(jù)、敏感數(shù)據(jù)等),因?yàn)?,如果是這樣使用,無(wú)論是在開發(fā)環(huán)境,還是測(cè)試環(huán)境,還是生產(chǎn)環(huán)境中,這些輸出語(yǔ)句都將輸出相關(guān)信息,而刪除或添加這些輸出語(yǔ)句的操作成本比較高,操作可行性低。
推薦的做法是使用日志框架來(lái)輸出相關(guān)信息!
當(dāng)添加了Lombok依賴后,可以在需要使用日志的類上添加`@Slf4j`注解,然后,在類的任意中,均可使用名為`log`的變量,且調(diào)用其方法來(lái)輸出日志(名為`log`的變量也是Lombok框架在編譯期自動(dòng)補(bǔ)充的聲明并創(chuàng)建對(duì)象)!
在Slf4j日志框架中,將日志的可顯示級(jí)別根據(jù)其重要程度(嚴(yán)重程度)由低到高分為:
在配置文件中,可以通過(guò)`logging.level.包名.類名`來(lái)設(shè)置當(dāng)前類的日志顯示級(jí)別,例如:
// yml文件寫法logging: level: cn.celinf.boot.demo: info// .properties文件格式寫法logging.level.cn.celinf.boot.demo: info
當(dāng)設(shè)置了顯示的日志級(jí)別后,僅顯示設(shè)置級(jí)別和更重要的級(jí)別的日志,例如,設(shè)置為`info`時(shí),只顯示`info`、`warn`、`error`,不會(huì)顯示`debug`、`trace`級(jí)別的日志!
當(dāng)輸出日志時(shí),通過(guò)`log`變量調(diào)用`trace()`方法輸出的日志就是`trace`級(jí)別的,調(diào)用`debug()`方法輸出的日志就是`debug()`級(jí)別的,以此類推,可調(diào)用的方法還有`info()`、`warn()`、`error()`。
在開發(fā)實(shí)踐中,關(guān)鍵數(shù)據(jù)和敏感數(shù)據(jù)都應(yīng)該通過(guò)`trace()`或`debug()`進(jìn)行輸出,在開發(fā)環(huán)境中,可以將日志的顯示級(jí)別設(shè)置為`trace`,則會(huì)顯示所有日志,當(dāng)需要交付到生產(chǎn)環(huán)境中時(shí),只需要將日志的顯示級(jí)別調(diào)整為`info`即可!
默認(rèn)情況下,日志的顯示級(jí)別是`info`,所以,即使沒有在配置文件中進(jìn)行正確的配置,所有info、warn、error級(jí)別的日志都會(huì)輸出顯示。
- 在配置時(shí),屬性名稱中的`logging.level`部分是必須的,在其后,必須寫至少1級(jí)包名
- 發(fā)實(shí)踐中,屬性名稱通常配置為`logging.level.項(xiàng)目根包`
在使用Slf4j時(shí),通過(guò)`log`調(diào)用的每種級(jí)別的方法都被重載了多次(各級(jí)別對(duì)應(yīng)除了方法名稱不同,重載的次數(shù)和參數(shù)列表均相同),推薦使用的方法是參數(shù)列表為`(String format, Object… arguments)`的,例如:
public void trace(String format, Object… arguments);public void debug(String format, Object… arguments);public void info(String format, Object… arguments);public void warn(String format, Object… arguments);public void error(String format, Object… arguments);
以上方法中,第1個(gè)參數(shù)是將要輸出的字符串的模式(模版),在此字符串中,如果需要包含某個(gè)變量值,則使用`{}`表示,如果有多個(gè)變量值,均是如此,然后,再通過(guò)第2個(gè)參數(shù)(是可變參數(shù))依次表示各`{}`對(duì)應(yīng)的值,例如:
log.debug(“加密前的密碼:{},加密后的密碼:{}”, password, encodedPassword);
使用這種做法,可以避免多變量時(shí)頻繁的拼接字符串,另外,日志框架會(huì)將第1個(gè)參數(shù)進(jìn)行緩存,以此提高后續(xù)每一次的執(zhí)行效率。
在開發(fā)實(shí)踐中,應(yīng)該對(duì)程序執(zhí)行關(guān)鍵位置添加日志的輸出,通常包括:
其實(shí),Slf4j日志框架只是日志的一種標(biāo)準(zhǔn),并不是具體的實(shí)現(xiàn)(感覺上與Java中的接口有點(diǎn)相似),常見有具體實(shí)現(xiàn)了日志功能的框架有l(wèi)og4j、logback等,為了統(tǒng)一標(biāo)準(zhǔn),所以才出現(xiàn)了Slf4j,同時(shí),由于log4j、logback等框架實(shí)現(xiàn)功能并不統(tǒng)一,所以,Slf4j提供了對(duì)主流日志框架的兼容,在Spring Boot工程中,`spring-boot-starter`就已經(jīng)依賴了`spring-boot-starter-logging`,而在此依賴下,通常包括Slf4j、具體的日志框架、Slf4j對(duì)具體日志框架的兼容。
密碼加密(額外知識(shí)點(diǎn))
【這并不是Spring Boot框架的知識(shí)點(diǎn)】
對(duì)密碼進(jìn)行加密,可以有效的保障密碼安全,即使出現(xiàn)數(shù)據(jù)庫(kù)泄密,密碼安全也不會(huì)受到影響!為了實(shí)現(xiàn)此目標(biāo),需要在對(duì)密碼進(jìn)行加密時(shí),使用不可逆的算法進(jìn)行處理!
通常,不可以使用加密算法對(duì)密碼進(jìn)行加密碼處理,從嚴(yán)格定義上來(lái)看,所有的加密算法都是可以逆向運(yùn)算的,即同時(shí)存在加密和解密這2種操作,加密算法只能用于保證傳輸過(guò)程的安全,并不應(yīng)該用于保證需要存儲(chǔ)下來(lái)的密碼的安全!
哈希算法都是不可逆的,通常,用于處理密碼加密的算法中,典型的是一些消息摘要算法,例如MD5、SHA256或以上位數(shù)的算法。
消息摘要算法的主要特征有:
在消息摘要算法中,以MD5為例,其運(yùn)算結(jié)果是一個(gè)128位長(zhǎng)度的二進(jìn)制數(shù),通常會(huì)轉(zhuǎn)換成十六進(jìn)制數(shù)顯示,所以是32位長(zhǎng)度的十六進(jìn)制數(shù),MD5也被稱之為128位算法。理論上,會(huì)存在2的128次方種類的摘要結(jié)果,且對(duì)應(yīng)2的128次方種不同的消息,如果在未超過(guò)2的128次方種消息中,存在2個(gè)或多個(gè)不同的消息對(duì)應(yīng)了相同的摘要,則稱之為:發(fā)生了碰撞。一個(gè)消息摘要算法是否安全,取決其實(shí)際的碰撞概率,關(guān)于消息摘要算法的破解,也是研究其碰撞概率。
存在窮舉消息和摘要的對(duì)應(yīng)關(guān)系,并利用摘要在此對(duì)應(yīng)關(guān)系進(jìn)行查詢,從而得知消息的做法,但是,由于MD5是128位算法,全部窮舉是不可能實(shí)現(xiàn)的,所以,只要原始密碼(消息)足夠復(fù)雜,就不會(huì)被收錄到所記錄的對(duì)應(yīng)關(guān)系中去!
為了進(jìn)一步提高密碼的安全性,在使用消息摘要算法進(jìn)行處理時(shí),通常還會(huì)加鹽!鹽值可以是任意的字符串,用于與密碼一起作為被消息摘要算法運(yùn)算的數(shù)據(jù)即可,例如:
@Component // 實(shí)例化到spring容器中public class PasswordEncoder { /** * 加密 * @param rawPassword 源密碼 * @return */ public String encode(String rawPassword) { //鹽 UUID不重復(fù)隨機(jī)字符串 String salt = UUID.randomUUID().toString().replace(“-“,””); String encodedPassword = DigestUtils.md5DigestAsHex((salt+rawPassword).getBytes()); return salt+encodedPassword; }/** * 密碼對(duì)比 * @param rawPassword 原密碼 * @param encodedPassword 加密的密碼 * @return */ public Boolean decode(String rawPassword,String encodedPassword) { //鹽 String salt = encodedPassword.substring(0,32); String newPassword = DigestUtils.md5DigestAsHex((salt+rawPassword).getBytes()); //判斷是否相等 return newPassword.equals(encodedPassword); }}
加鹽的目的是使得被運(yùn)算數(shù)據(jù)變得更加復(fù)雜,鹽值本身和用法并沒有明確要求!
甚至,在某些用法或算法中,還會(huì)使用隨機(jī)的鹽值,則可以使用完全相同的原消息對(duì)應(yīng)的摘要卻不同!
> 推薦了解:預(yù)計(jì)算的哈希鏈、彩虹表、雪花算法。
為了進(jìn)一步保證密碼安全,還可以使用多重加密,即反復(fù)調(diào)用消息摘要算法。
除此以外,還可以使用安全系數(shù)更高的算法,例如SHA-256是256位算法,SHA-384是384位算法,SHA-512是512位算法。
Validation框架
當(dāng)客戶端向服務(wù)器提交請(qǐng)求時(shí),如果請(qǐng)求數(shù)據(jù)出現(xiàn)明顯的問(wèn)題(例如關(guān)鍵數(shù)據(jù)為`null`、字符串的長(zhǎng)度不在可接受范圍內(nèi)、其它格式錯(cuò)誤),應(yīng)該直接響應(yīng)錯(cuò)誤,而不是將明顯錯(cuò)誤的請(qǐng)求參數(shù)傳遞到Service!
> 關(guān)于判斷錯(cuò)誤,只有涉及數(shù)據(jù)庫(kù)中的數(shù)據(jù)才能判斷出結(jié)果的,都由Service進(jìn)行判斷,而基本的格式判斷,都由Controller進(jìn)行判斷。
Validation框架是專門用于解決檢查數(shù)據(jù)基本格式有效性的,最早并不是Spring系列的框架,目前,Spring Boot提供了更好的支持,所以,通常結(jié)合在一起使用。
在Spring Boot項(xiàng)目中,需要添加`spring-boot-starter-validation`依賴項(xiàng),例如:
//依賴項(xiàng) org.springframework.boot spring-boot-starter-validation
在控制器中,首先,對(duì)需要檢查數(shù)據(jù)格式的請(qǐng)求參數(shù)添加`@Valid`或`@Validated`注解(這2個(gè)注解沒有區(qū)別),例如:
@RequestMapping(“/add-new”)public JsonResult addNew(@Validated AdminAddNewDTO adminAddNewDTO) { adminService.addNew(adminAddNewDTO); return JsonResult.ok();}
真正需要檢查的是`AdminAddNewDTO`中各屬性的值,所以,接下來(lái)需要在此類的各屬性上通過(guò)注解來(lái)配置檢查的規(guī)則,例如:
@Datapublic class AdminAddNewDTO implements Serializable {@NotNull // 驗(yàn)證規(guī)則為:不允許為nullprivate String username;@NotNullprivate String password;// ===== 原有其它代碼 =====}
重啟項(xiàng)目,通過(guò)不提交用戶名的URL(例如:http://localhost:8080/admins/add-new)進(jìn)行訪問(wèn),在瀏覽器上會(huì)出現(xiàn)400錯(cuò)誤頁(yè)面,并且,在IntelliJ IDEA的控制臺(tái)會(huì)出現(xiàn)以下警告:
//警告2022-06-07 11:37:53.424 WARN [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException:org.springframework.validation.BeanPropertyBindingResult: 1 errorsField error in object ‘adminAddNewDTO’ on field ‘username’: rejected value [null]; codes [NotNull.adminAddNewDTO.username,NotNull.username,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [adminAddNewDTO.username,username]; arguments []; default message [username]]; default message [不能為null]]
從警告信息中可以看到,當(dāng)驗(yàn)證失敗時(shí)(不符合所使用的注解對(duì)應(yīng)的規(guī)則時(shí)),會(huì)出現(xiàn)`org.springframework.validation.BindException`異常,則自行處理此異常即可!
首先,在`State`中添加新的枚舉:
public enum State {OK(200),ERR_USERNAME(201),ERR_PASSWORD(202),ERR_BAD_REQUEST(400), // 新增ERR_INSERT(500);// ===== 原有其它代碼 =====}
然后,在`GlobalExceptionHandler`中添加新的處理異常的方法:
@ExceptionHandler(BindException.class)public JsonResult handleBindException(BindException e) {return JsonResult.fail(State.ERR_BAD_REQUEST, e.getMessage());}
關(guān)于錯(cuò)誤提示信息,以上內(nèi)容中出現(xiàn)了`不能為null`的字樣,是默認(rèn)的提示文本,可以通過(guò)`@NotNull`注解的`message`屬性進(jìn)行配置,例如:
@Datapublic class AdminAddNewDTO implements Serializable { @NotNull(message = “添加管理員失敗,請(qǐng)?zhí)峤挥脩裘?#8221;) private String username; @NotNull(message = “添加管理員失敗,請(qǐng)?zhí)峤幻艽a!”) private String password;// ===== 原有其它代碼 =====}
然后,在處理異常時(shí),通過(guò)異常信息獲取自定義的提示文本:
@ExceptionHandler(BindException.class)public JsonResult handleBindException(BindException e) {BindingResult bindingResult = e.getBindingResult();String defaultMessage = bindingResult.getFieldError().getDefaultMessage();return JsonResult.fail(State.ERR_BAD_REQUEST, defaultMessage);}
再次運(yùn)行,在不提交用戶名和密碼的情況下,會(huì)隨機(jī)的提示用戶名或密碼驗(yàn)證失敗的提示文本中的某1條。
在Validation框架中,還有其它許多注解,用于進(jìn)行不同格式的驗(yàn)證,例如:
以上注解,包括`@NotNull`是允許疊加使用的,即允許在同一個(gè)參數(shù)屬性上添加多個(gè)注解!
以上注解均可以配置`message`屬性,用于指定驗(yàn)證失敗的提示文本。
通常:(開發(fā)中)
常用檢驗(yàn)注解
學(xué)習(xí)記錄,如有侵權(quán)請(qǐng)聯(lián)系刪除