文:答應我,別再if/else校驗請求參數(shù)了可以嗎
注:中國傳統(tǒng)文化素邪,先仔細看外莲,若有用,再點贊兔朦, 給自己一點思考的時間
注:微信搜索:CodeCow偷线,關注這個非常 SAO 的程序員
?哎!彈指之間
遙想當年沽甥,其實我也特別鐘情于 if/else 連環(huán)寫法声邦,來 校驗請求入?yún)?/strong>,上來就是一頓SAO操作:
就現(xiàn)在來說摆舟,我們項目都是前后端分離翔忽,前后端約定好請求參數(shù)英融,封裝成一個對象,前段根據(jù)對象來傳參歇式,但傳入的參數(shù)是否為空,怎么判斷胡野!
比如舉個好理解的簡單例子:
請求參數(shù)
@Data
@ApiModel(value = "登錄請求參數(shù)")
public class LoginReqDTO {
@ApiModelProperty(value = "手機號")
private String mobile;
@ApiModelProperty(value = "密碼")
private String password;
@ApiModelProperty(value = "驗證碼")
private String code;
}
響應結果
@Data
public class RespResult<T> {
private Integer code;
private String message;
private T data;
}
當前端調用我們的登錄接口時材失,我們需要 判斷請求參數(shù)是否為空,這時候 SAO代碼 出現(xiàn)了:
@RestController
public class LoginController{
@ApiOperation(value = "登錄接口")
@PostMapping(value = "/login")
public RespResult login(LoginReqDTO loginReqDTO) {
if (StringUtils.isEmpty(loginReqDTO.getMobile())) {//判斷手機號是否為空
return new RespResult(400, "手機號不能為空");
} else if (StringUtils.isEmpty(loginReqDTO.getPassword())) {//判斷密碼是否為空
return new RespResult(400, "密碼不能為空");
} else if (StringUtils.isEmpty(loginReqDTO.getCode())) {//判斷驗證碼是否為空
return new RespResult(400, "驗證碼不能為空");
} else {
return new RespResult(200, "成功");//我的嗎硫豆,終于成功了 龙巨!
}
}
}
臥CAO!現(xiàn)在請求對象里的參數(shù)只有三個熊响,當參數(shù)有幾十個時旨别,那幾十個 if/else 嵌套,不累嗎汗茄,那可以說是 非常酸爽了……
那么秸弛,問題來了:
第一點:你是爽了,別人一閱讀(怕是上來就是一 Jao洪碳!)
第二點:現(xiàn)在是一個接口递览,那幾十個接口時,那全屏可能只有 if/else了瞳腌。(喲呵绞铃!膩害)
第三點:則是以后如果再復雜一點,或者想要再加條件的話嫂侍,是不是還的整個if/else儿捧,極其不好擴展。
第四點:最后代碼若一改挑宠,以前的老功能肯定還得重測菲盾,豈不瘋了……
所以,如果在不看下文的情況下痹栖,你一般會如何去對付這些令人頭痛的if/else語句呢亿汞?
當然有人會說用 循環(huán)語句switch/case 來判斷是否會優(yōu)雅一些呢?答案是:有錘子區(qū)別揪阿,毛區(qū)別都沒有疗我!
接下來簡單講幾種改進方式,別再 if/else走天下了
有Boot自帶的參數(shù)驗證為啥不用
大家肯定學過 boot吧(沒學過也不打緊南捂,沒吃過豬肉吴裤,還沒見過豬跑嗎!D缃 )麦牺,因此,為啥不用boot自帶的 spring validation,也就是@Validated注解剖膳,為啥不用魏颓?
首先我們在請求參數(shù)上加上 @NotBlank注解,并定義為空時的 message
@Data
@ApiModel(value = "登錄請求參數(shù)")
public class LoginReqDTO {
@NotBlank(message = "手機號不能為空")
@ApiModelProperty(value = "手機號")
private String mobile;
@NotBlank(message = "密碼不能為空")
@ApiModelProperty(value = "密碼")
private String password;
@NotBlank(message = "驗證碼不能為空")
@ApiModelProperty(value = "驗證碼")
private String code;
}
接下來我們將 請求參數(shù)是否為空 交給 spring validation 來判斷吱晒,只需要定義一個 全局異常處理器來處理異常:
GlobalExceptionHandler :
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = BindException.class)
public RespResult violationException(BindException exception) {
// 不帶任何參數(shù)訪問接口,會拋出 BindException
// 因此甸饱,我們只需捕獲這個異常,并返回我們設置的 message 即可
String message = exception.getAllErrors().get(0).getDefaultMessage();
return new RespResult(400, message);
}
}
接下來接口調用就變得非常簡單了仑濒,加個@Validated注解就行了叹话, if/else也灰飛煙滅了:
@RestController
public class LoginController {
@ApiOperation(value = "登錄接口")
@PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
public RespResult login(@Validated LoginReqDTO loginReqDTO) {
return new RespResult(200, "成功");
}
}
最后,我們用 空的請求參數(shù) 使用Postman訪問下登錄接口:localhost:8080/login墩瞳;結果為:
{
"code": 400,
"message": "手機號不能為空",
"data": null
}
是不是感覺很爽驼壶,SAO代碼( if/else)也沒了。
而且喉酌,這樣一來热凹,假如以后我想擴充條件,只需要去“請求參數(shù)對象中添加一個@NotBlank(message = “XXX”)注解”即可瞭吃,而不是去改以前的代碼碌嘀,這豈不很穩(wěn)!
example:
@NotBlank(message = "XXX不能為空")
private String XXX;
穩(wěn)是穩(wěn)了歪架,但是股冗,假如請求參數(shù)不是對象怎么辦?
比如和蚪,稍微老一點的項目請求參數(shù)可能是非實體止状,那此時的 if/else 是怎么樣被 KO 的呢 !
別慌!此時我們只需要簡單改造下GlobalExceptionHandler——全局異常處理器:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ConstraintViolationException.class})
public RespResult violationException(Exception exception) {
if (exception instanceof ConstraintViolationException) { //使用關鍵字instanceof 判斷 ConstraintViolationException 是否為 Exception 直接或間接子類
return constraintViolationException((ConstraintViolationException) exception); //調用下面方法攒霹,返回結果
}
return new RespResult(500, "server error"); // 否則跑出 server error
}
// 當我們沒有此方法怯疤,空參訪問localhost:8080/login 會拋出ConstraintViolationException 異常
public RespResult constraintViolationException(ConstraintViolationException ex) {
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if (!CollectionUtils.isEmpty(constraintViolations)) { //判斷是否為空
StringBuilder sb = new StringBuilder();
for (ConstraintViolation constraintViolation : constraintViolations) { //遍歷 ConstraintViolation
sb.append(constraintViolation.getMessage()).append(","); // 吧錯誤信息循環(huán)放到sb中, 并以逗號隔開
}
String errorMessage = sb.toString(); // 獲得異常信息字符串
return new RespResult(400, errorMessage);
}
return new RespResult(500, "server error"); // 否則跑出 server error
}
}
接下來,我們只需要在請求參數(shù) 前加上“** @NotBlank** ”注解催束,并且在接口所在類加上“ @Validated ” 即可集峦,if/else 同樣被 KO 了
@Validated // 此注解別忘了
@RestController
public class LoginController {
@ApiOperation(value = "登錄接口")
@PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
public RespResult login(@NotBlank(message = "手機號不能為空") String mobile,
@NotBlank(message = "密碼不能為空") String password,
@NotBlank(message = "驗證碼不能為空") String code) {
return new RespResult(200, "成功");
}
}
我們再次用 空的請求參數(shù) 使用Postman訪問下登錄接口:localhost:8080/login;結果為:
{
"code": 400,
"message": "驗證碼不能為空,密碼不能為空,手機號不能為空,",
"data": null
}
不難發(fā)現(xiàn)抠刺,把請求參數(shù)中所有為空的參數(shù)塔淤,都驗證出來了,它不香嗎速妖!
然并卵高蜂,實際開發(fā)中,并非 3 + 2 - 5 * 0 這么簡單
假如有需求罕容,校驗請求“手機號格式是否正確”备恤,怎么辦稿饰!別慌,自定義注解 登場
有自定義注解為啥不用
首先露泊,我們要自定義注解喉镰,肯定的先了解注解的本質。
在「java.lang.annotation.Annotation」接口中有這么一句話滤淳,用來描述『注解』梧喷。
The common interface extended by all annotation types
譯:所有的注解類型都繼承自這個普通的接口(Annotation)
這句話有點抽象,但卻說出了注解的本質脖咐。
來來來,我們隨便看一個JDK內置注解的定義汇歹,比如咋們常用的:@Override
@Target(ElementType.METHOD) //注解放置的目標位置
@Retention(RetentionPolicy.SOURCE) //注解在哪個階段執(zhí)行
public @interface Override { //注解
}
不難看出屁擅,這是注解 @Override 的定義,其實它本質上就是:
public interface Override extends Annotation{ //繼承 Annotation
}
你沒有看錯产弹,注解的本質就是一個繼承了 Annotation 接口的接口
小編有幸在書上看到這樣一端描述:
- 一個注解準確意義上來說派歌,只不過是一種特殊的注釋而已,如果沒有解析它的代碼痰哨,它可能連注釋都不如
注解小編就淺聊到這里了胶果,咋們言歸正傳,回到 **使用自定義注解 KO if/else ** 斤斧,也別讓 if/else 久等了早抠。
首先,自定一個參數(shù)驗證注解:@ValidParam
/**
* Create By CodeCow on 2020/7/21.
* 自定義注解
* @Target:注解放置的目標位置
* @Retention:注解在哪個階段執(zhí)行
* @Constraint:指定此注解的實現(xiàn)撬讽, 即:驗證器(就是下面的:ParamValidator 類)
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ParamValidator.class)
public @interface ValidParam {
String message() default "手機號格式不正確"; // 校驗的失敗的時候返回的信息,可以指定默認值
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
接下來我們 編寫一個校驗器 蕊连,驗證該注解
/**
* Create By CodeCow on 2020/7/21.
* 編寫校驗器 驗證該上面我們自定義的注解:@ValidParam
* 我們需要 實現(xiàn) ConstraintValidator 接口 并且重寫 isValid 方法
*/
public class ParamValidator implements ConstraintValidator<ValidParam, String> {
@Override
public void initialize(ValidParam constraintAnnotation) {
// 初始化
}
@Override
public boolean isValid(String param, ConstraintValidatorContext constraintValidatorContext) {
// 開始驗證
// 手機號格式(正則語法)
String mobileFormat = "^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,1,3,6-8])|" +
"(18[0-9])|(19[8,9])|(166))[0-9]{8}$";
Pattern pattern = Pattern.compile(mobileFormat);
return pattern.matcher(param).matches(); // 手機號格式正確返回 true,否則 false
}
}
最后游昼,我們修改登錄接口甘苍,在 請求參數(shù) 前,加上我們自定義的注解(@ValidParam)即可:
同樣烘豌,請求參數(shù)的校驗载庭,只需一個注解, if/else就被 KO 了
@RestController
public class LoginController {
@ApiOperation(value = "登錄接口")
@PostMapping(value = "/login", consumes = "application/json", produces = "application/json")
public RespResult login(@ValidParam String mobile) {
return new RespResult(200, "成功");
}
}
最后廊佩,我們再次以 空參數(shù) 使用Postman訪問下登錄接口:localhost:8080/login囚聚;結果為:
{
"code": 400,
"message": "手機號格式不正確,",
"data": null
}
后記
好啦,今就先聊到這里吧罐寨,本文僅僅是 拋磚引玉 而已靡挥,使用了現(xiàn)階段大家比較“鐘情的 if/else” 打了個樣,不是說用 if/else 不好鸯绿,只是希望大家在以后的編碼中跋破,不要濫用簸淀,代碼不要太過“冗余”。
其次毒返,在真實項目中租幕,業(yè)務場景不可能像上面那么簡單,也就像小編前文所提(實際開發(fā)拧簸,并非 3 + 2 - 5 * 0 這么簡單)劲绪,所以在您握住鼠標的那一刻,還是的多思考一番盆赤,考慮這樣寫是否合理贾富,是否具有擴展性。
好文推薦
小聲BB
- 中國傳統(tǒng)文化牺六,先仔細看颤枪,若有用,再點贊淑际, 給自己一點思考的時間
- 更多幽默畏纲、風趣好文,盡在“ CodeCow ” 程序牛公眾號