瞧瞧別人家的引數校驗,那叫一個優雅!

苏三说技术發表於2024-12-03

前言

對於開發人員來說,對使用者輸入的引數或者系統引數做校驗,是日常工作之一。

很多小夥伴在寫介面的時候,可能都會碰到一個問題:引數校驗應該怎麼寫?

比如,開發一個使用者註冊介面,需要校驗以下條件:

  • 使用者名稱不能為空,長度在 3 到 20 個字元之間;
  • 密碼不能為空,長度至少為 8 個字元;
  • 年齡必須是正整數,不能超過 120;
  • 郵箱必須符合標準格式。

乍一看,這種校驗邏輯看起來很簡單嘛,直接寫幾個 if 就完事了。

但真的這麼簡單嗎?

接下來我們就從傳統的引數校驗入手,看看問題出在哪,然後再聊聊 Spring Boot 中如何優雅地實現引數校驗,希望對你會有所幫助。

一、傳統引數校驗的問題

很多人可能會直接在 Controller 裡手寫校驗邏輯,比如下面這個程式碼:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping("/register")
    public ResponseEntity<String> register(@RequestBody Map<String, Object> request) {
        String username = (String) request.get("username");
        if (username == null || username.length() < 3 || username.length() > 20) {
            return ResponseEntity.badRequest().body("使用者名稱不能為空,且長度必須在3到20之間");
        }

        String password = (String) request.get("password");
        if (password == null || password.length() < 8) {
            return ResponseEntity.badRequest().body("密碼不能為空,且長度至少為8個字元");
        }

        Integer age = (Integer) request.get("age");
        if (age == null || age <= 0 || age > 120) {
            return ResponseEntity.badRequest().body("年齡必須是正整數,且不能超過120");
        }

        return ResponseEntity.ok("註冊成功!");
    }
}

這段程式碼乍一看沒什麼問題,但如果仔細分析,會發現一堆隱患:

  1. 程式碼冗餘:校驗邏輯散落在 Controller 裡,寫起來麻煩,後期維護更是災難。
  2. 重複勞動:類似的校驗邏輯可能會出現在多個介面裡,導致程式碼重複度極高。
  3. 使用者體驗差:返回的錯誤資訊不統一、不規範,前端開發還得猜使用者輸入到底哪兒錯了。
  4. 擴充套件性差:萬一某天需要加新的校驗規則,你可能要到處改程式碼。

所以,這種手寫引數校驗的方式,在簡單場景下勉強能用,但如果業務變複雜,問題會越來越多。

那麼問題來了,那有沒有更優雅的方式來處理這些問題呢?

答:當然是有的。

二、Spring Boot 的引數校驗機制

在 Spring Boot 中,我們可以使用 Hibernate Validator(Bean Validation 的參考實現)來實現引數校驗。

它的核心思路是:把校驗邏輯從業務程式碼裡抽離出來,用註解的方式宣告校驗規則

接下來我們一步步來看怎麼實現。

1. 使用註解進行引數校驗

首先,定義一個用於接收使用者註冊引數的 DTO 物件:

@Data
public class UserRegistrationRequest {

    @NotNull(message = "使用者名稱不能為空")
    @Size(min = 3, max = 20, message = "使用者名稱長度必須在3到20之間")
    private String username;

    @NotNull(message = "密碼不能為空")
    @Size(min = 8, message = "密碼長度至少為8個字元")
    private String password;

    @NotNull(message = "年齡不能為空")
    @Min(value = 1, message = "年齡必須是正整數")
    @Max(value = 120, message = "年齡不能超過120")
    private Integer age;

    @Email(message = "郵箱格式不正確")
    private String email;
}

這裡我們用了幾個常見的校驗註解:

  • @NotNull:欄位不能為空;
  • @Size:限制字串長度;
  • @Min@Max:限制數值範圍;
  • @Email:校驗郵箱格式。

這些註解由 Hibernate Validator 提供,基本涵蓋了日常開發中的大部分校驗需求。

然後,在 Controller 中這樣寫:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping("/register")
    public ResponseEntity<String> register(@Valid @RequestBody UserRegistrationRequest request) {
        return ResponseEntity.ok("註冊成功!");
    }
}

注意這裡的 @Valid 註解,它的作用是告訴 Spring:對請求引數進行校驗

2. 統一處理校驗錯誤

如果前端傳的引數不合法,Spring 會丟擲一個 MethodArgumentNotValidException 異常。預設情況下,這個異常返回的資訊不太友好,可能是這樣的:

{
  "timestamp": "2024-01-01T12:00:00.000+00:00",
  "status": 400,
  "error": "Bad Request",
  "message": "Validation failed for object='userRegistrationRequest'. Error count: 2",
  "path": "/api/users/register"
}

為了提升使用者體驗,我們可以用全域性異常處理器來統一格式化錯誤資訊:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationException(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            errors.put(error.getField(), error.getDefaultMessage());
        });
        return ResponseEntity.badRequest().body(errors);
    }
}

現在,當引數校驗失敗時,返回的錯誤資訊會變成這樣:

{
  "username": "使用者名稱長度必須在3到20之間",
  "password": "密碼不能為空"
}

清晰又直觀,使用者一看就明白自己錯在哪兒了。

最近建了一些工作內推群,收集了不少工作崗位,加我微信:su_san_java,備註:部落格園+所在城市,即可進群。

三、應對複雜場景的高階技巧

1. 分組校驗

有些場景下,不同的介面對引數的校驗規則是不一樣的,比如:

  • 註冊介面要求 usernamepassword 是必填項;
  • 更新介面只需要校驗 emailage

這種情況下,可以用 分組校驗 來解決。

定義校驗分組

public interface RegisterGroup {}
public interface UpdateGroup {}

在欄位上指定分組

public class UserRequest {

    @NotNull(groups = RegisterGroup.class, message = "使用者名稱不能為空")
    @Size(min = 3, max = 20, groups = RegisterGroup.class, message = "使用者名稱長度必須在3到20之間")
    private String username;

    @NotNull(groups = RegisterGroup.class, message = "密碼不能為空")
    private String password;

    @Email(groups = UpdateGroup.class, message = "郵箱格式不正確")
    private String email;

    @Min(value = 1, groups = UpdateGroup.class, message = "年齡必須是正整數")
    private Integer age;
}

在 Controller 中指定分組

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping("/register")
    public ResponseEntity<String> register(@Validated(RegisterGroup.class) @RequestBody UserRequest request) {
        return ResponseEntity.ok("註冊成功!");
    }

    @PutMapping("/update")
    public ResponseEntity<String> update(@Validated(UpdateGroup.class) @RequestBody UserRequest request) {
        return ResponseEntity.ok("更新成功!");
    }
}

2. 自定義校驗註解

如果 Hibernate Validator 提供的註解不能滿足需求,還可以自定義校驗註解。例如,校驗手機號格式。

定義註解

@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhone {
    String message() default "手機號格式不正確";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

實現校驗邏輯

public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {

    private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.matches(PHONE_REGEX);
    }
}

使用自定義註解

public class UserRequest {
    @ValidPhone
    private String phone;
}

四、總結

優雅的引數校驗不僅能提高程式碼的可維護性,還能顯著提升使用者體驗。

在 Spring Boot 中,透過使用 Hibernate Validator 提供的註解,配合分組校驗、自定義校驗和統一異常處理。

我們可以輕鬆實現簡潔、高效、可擴充套件的引數校驗機制。

優雅的引數校驗的秘籍是:

  1. 註解優先:能用註解解決的校驗,就不要手寫邏輯程式碼。
  2. 分離校驗邏輯:引數校驗應該集中在 DTO 層,避免散落在業務程式碼中。
  3. 全域性統一異常處理:確保錯誤資訊規範化、友好化。
  4. 合理使用分組校驗:根據介面需求靈活調整校驗規則。
  5. 覆蓋邊界條件:透過單元測試驗證校驗邏輯,確保沒有漏網之魚。

如果看了這篇文章有些收穫,記得給我點贊和轉發喔。

最後說一句(求關注,別白嫖我)

如果這篇文章對您有所幫助,或者有所啟發的話,幫忙關注一下我的同名公眾號:蘇三說技術,您的支援是我堅持寫作最大的動力。
求一鍵三連:點贊、轉發、在看。
關注公眾號:【蘇三說技術】,在公眾號中回覆:進大廠,可以免費獲取我最近整理的10萬字的面試寶典,好多小夥伴靠這個寶典拿到了多家大廠的offer。

相關文章