參數驗證是防禦性編程核心。本文對比 Java (Guava Preconditions) 與 C# (FluentValidation) 兩大生態主流驗證庫,展示聲明式驗證、異常信息攜帶、複雜規則組合、單元測試友好等最佳實踐。
| 方法 | 用途 | 失敗拋出 |
| checkArgument(boolean, msg, args...) | 驗證參數滿足條件 | IllegalArgumentException |
| checkNotNull(obj, msg, args...) | 驗證非 null | NullPointerException |
| checkState(boolean, msg, args...) | 驗證對象狀態 (非參數) | IllegalStateException |
| checkElementIndex/PositionIndex | 驗證索引合法性 | IndexOutOfBoundsException |
實戰代碼
import com.google.common.base.Preconditions;
public class UserService {
public void register(String username, int age, String email) {
// 1. 基礎非空/非空字符串
Preconditions.checkNotNull(username, "用戶名不能為 null");
Preconditions.checkArgument(!username.trim().isEmpty(), "用戶名不能為空");
// 2. 範圍驗證
Preconditions.checkArgument(age > 0 && age < 150, "年齡必須在 1-149 之間,實際:%s", age);
// 3. 格式驗證 (正則)
Preconditions.checkArgument(
email.matches("^[\\w.-]+@[\\w.-]+\\.\\w+$"),
"郵箱格式不合法:%s", email
);
// 4. 業務規則驗證
Preconditions.checkState(!userRepo.exists(username), "用戶名已存在:%s", username);
// 通過驗證,執行業務邏輯
userRepo.save(new User(username, age, email));
}
}
優點與局限
- ✅ 代碼極簡潔、可讀性強、零依賴侵入 (僅 Guava)。
- ✅ 異常信息自帶參數值,除錯極其方便。
- ✅ 適用於**方法入口防禦性檢查**、私有方法內部斷言。
- ❌ 無分組驗證、無國際化、無註解聲明式綁定 (如 Bean Validation)。
- ❌ 複雜對象圖驗證需手動遞歸調用。
C#:FluentValidation — 強大的聲明式驗證框架
NuGet 安裝
Install-Package FluentValidation
Install-Package FluentValidation.AspNetCore (ASP.NET Core 整合)
實體與驗證器分離
// 實體類 (POCO)
public class Person {
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public Address Address { get; set; }
}
public class Address {
public string City { get; set; }
public string ZipCode { get; set; }
}
// 驗證器類 (繼承 AbstractValidator)
public class PersonValidator : AbstractValidator {
public PersonValidator() {
// 基礎規則
RuleFor(x => x.Name)
.NotEmpty().WithMessage("姓名不能為空")
.Length(1, 50).WithMessage("姓名長度 1-50 字符");
RuleFor(x => x.Age)
.GreaterThan(0).WithMessage("年齡必須大於 0")
.LessThan(150).WithMessage("年齡不能超過 150");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("郵箱不能為空")
.EmailAddress().WithMessage("郵箱格式不正確");
// 嵌套對象驗證
RuleFor(x => x.Address)
.NotNull().WithMessage("地址不能為空")
.SetValidator(new AddressValidator()); // 復用子驗證器
// 條件驗證
RuleFor(x => x.Email)
.NotEmpty().When(x => x.Age >= 18).WithMessage("成年人必須提供郵箱");
// 自定義驗證邏輯
RuleFor(x => x.Name)
.Must(BeUniqueName).WithMessage("姓名已被佔用");
}
private bool BeUniqueName(string name) {
// 調用 Repository 檢查
return !userRepo.Exists(name);
}
}
public class AddressValidator : AbstractValidator {
public AddressValidator() {
RuleFor(x => x.City).NotEmpty();
RuleFor(x => x.ZipCode).Matches(@"^\d{6}$").WithMessage("郵編格式錯誤");
}
}
使用方式
// 手動驗證
var person = new Person { Name = "", Age = -5, Email = "invalid" };
var validator = new PersonValidator();
var result = validator.Validate(person);
if (!result.IsValid) {
foreach (var failure in result.Errors) {
Console.WriteLine($"屬性: {failure.PropertyName}, 錯誤: {failure.ErrorMessage}");
}
// 輸出:
// 屬性: Name, 錯誤: 姓名不能為空
// 屬性: Age, 錯誤: 年齡必須大於 0
// 屬性: Email, 錯誤: 郵箱格式不正確
}
// ASP.NET Core 自動驗證 (啟用後 Controller 參數自動驗證)
[ApiController]
public class UserController : ControllerBase {
[HttpPost]
public IActionResult Register([FromBody] Person person) {
// ModelState.IsValid 自動檢查,失敗自動返回 400 + 錯誤詳情
return Ok("註冊成功");
}
}
進階特性
| 特性 | 說明 |
| 規則集 | 同一驗證器定義多組規則 (如 RuleSet("register")),按場景啟用 |
| 異步驗證 | MustAsync 支援 DB 查詢、外部 API 調用 |
| 屬性級依賴注入 | 驗證器構造函數注入 Service/Repository |
| 客戶端驗證生成 | ASP.NET Core 整合可輸出 jQuery Validation/Unobtrusive 規則 |
| 本地化 | WithMessage 支援資源文件鍵,配合 IStringLocalizer |
| 測試輔助 | TestValidate 擴展方法單元測試極其方便 |
Java Bean Validation (JSR 380) — 企業級標準替代
若需註解聲明式、分組、國際化、Spring 整合,請用標準 Bean Validation:
@Entity
public class User {
@NotNull @Size(min=2, max=20) private String username;
@Min(1) @Max(120) private int age;
@Email private String email;
@Valid private Address address; // 級聯驗證
}
// Service 中
@Validated
public void register(@Valid User user) { ... }
// 全局異常處理
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e) {
return R.error(e.getBindingResult().getFieldErrors().stream()
.map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
.collect(Collectors.joining("; ")));
}
選型建議
| 場景 | 推薦方案 | 理由 |
| Java 簡單方法入口檢查 | Guava Preconditions | 零配置、極簡、異常信息豐富 |
| Java 實體/DTO 結構化驗證 | Bean Validation (Hibernate Validator) | 標準註解、Spring 深度整合、分組/國際化 |
| Java 複雜業務規則、異步驗證 | 自定義 Validator + Spring Events | 完全可控、可注入服務 |
| C# 實體驗證、ASP.NET Core 專案 | FluentValidation | 流式 API、規則集、異步、測試友好、客戶端生成 |
| C# 簡單檢查 | Guard Clauses / ArgumentNullException.ThrowIfNull() (.NET 6+) | 內建、零依賴 |
總結
- **永遠在方法邊界驗證參數**,失敗快、信息全、易除錯。
- Java 輕量場景用 Guava,企業級標準用 Bean Validation。
- C# 首選 FluentValidation,功能最全、生態最強。
- 驗證邏輯與業務邏輯分離:驗證器類 / 註解 / 斷言,保持 Service 純淨。
- 異常信息**必須包含具體參數值**,避免「參數錯誤」這類無效日誌。
- 上一篇文章: Java 讀取 HTTP 請求 Body 完整代碼示例
- 下一篇文章: Java 獲取客戶端信息:User-Agent 解析、作業系統、瀏覽器、IP 與 Servlet 核心屬性
Copyright ©
程式碼之家 All Rights Reserved