Spring 轉換與驗證

最後更新: 2023-11-28 10:33:04 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

Spring 將轉換、呈現格式化、物件繫結和驗證視為彼此獨立的操作。因此,要求參數可以轉換為具型別的屬性,同時仍能產生結構化錯誤,而不必將領域物件與 Web 控制器耦合。

轉換與格式化 #

ConversionService 是型別轉換的外觀。它可以先判斷是否有可用的轉換,然後依原始類別或 TypeDescriptor 執行轉換;後者會保留註解和巢狀泛型資訊。

擴充介面分別涵蓋不同需求:

  • Converter<S, T> 實作一次強型別轉換。
  • ConverterFactory<S, R> 為一組目標子型別建立轉換器,例如將 String 轉換為任意列舉型別。
  • GenericConverter 宣告可轉換的來源/目標型別配對集合,並接收 TypeDescriptor 值,以執行依賴內容的轉換。
  • ConditionalGenericConverter 也可以判斷自身是否符合特定的來源與目標。
java
final class StringToOrderId implements Converter<String, OrderId> {
    @Override
    public OrderId convert(String source) {
        return new OrderId(UUID.fromString(source.strip()));
    }
}

Formatter<T> 屬於呈現層。其 Printer 和 Parser 操作使用 Locale,因此適合數字、日期和面向使用者的文字。對於與內容無關的型別變更,請使用轉換器;當文字表示取決於地區設定或 UI 原則時,請使用格式化器。透過 FormatterRegistry 註冊兩者;DefaultFormattingConversionService 同時實作登錄表和轉換服務契約。

舊版 JavaBeans 屬性編輯器 #

Spring 仍為了相容性支援 java.beans.PropertyEditor。PropertyEditor 具有狀態,主要透過文字和內部值運作,且不是執行緒安全的。因此,容器不會共用執行個體,而是為每次繫結操作建立編輯器。

JavaBeans 慣例會在 Type 旁尋找名為 TypeEditor 的編輯器;PropertyEditorManager 也支援明確註冊和搜尋路徑。Spring 增加了用於依型別或屬性路徑註冊的 PropertyEditorRegistry,以及用於可重複使用註冊邏輯的 PropertyEditorRegistrar。BeanWrapper 和 DataBinder 會公開這些登錄表。

新的應用程式轉換應使用 ConversionService;整合要求使用屬性編輯器的 JavaBeans API 時,屬性編輯器仍然適用。HTTP 訊息本文通常不會由屬性編輯器解碼:Spring MVC 使用 HttpMessageConverter 處理 JSON 等本文格式。

使用 DataBinder 進行繫結 #

DataBinder 將輸入的屬性值套用至目標物件。它會解析巢狀屬性路徑、呼叫轉換器或屬性編輯器、將失敗記錄在 BindingResult 中,並且可以呼叫驗證器。

java
var binder = new DataBinder(command, "order");
binder.setAllowedFields("customerId", "quantity");
binder.setConversionService(conversionService);
binder.addValidators(new OrderValidator());
binder.bind(propertyValues);
binder.validate();

BindingResult result = binder.getBindingResult();

繫結不受信任的輸入是安全邊界。請定義允許的欄位,或使用專用命令物件,而不要公開過於寬泛的領域物件。轉換失敗會成為欄位錯誤,而不是要求例外逸出繫結操作。巢狀路徑和集合的自動擴充可以設定;對於攻擊者控制的輸入,應設定上限。

Spring 的 Validator 契約 #

Spring Validator 宣告其支援的類別,並透過 Errors 回報失敗:

java
final class OrderValidator implements Validator {
    public boolean supports(Class<?> type) {
        return OrderCommand.class.isAssignableFrom(type);
    }

    public void validate(Object target, Errors errors) {
        var order = (OrderCommand) target;
        ValidationUtils.rejectIfEmpty(errors, "customerId", "required");
        if (order.quantity() <= 0) {
            errors.rejectValue("quantity", "positive");
        }
    }
}

Errors.pushNestedPath() 和 popNestedPath() 讓複合驗證器能以正確路徑回報子物件錯誤。錯誤碼是邏輯識別碼;訊息解析之後可以選取本地化文字。

Jakarta Bean Validation #

Spring 6 整合了 jakarta.validation Bean Validation 3.x。LocalValidatorFactoryBean 會啟動 Hibernate Validator 等提供者,並同時實作 Jakarta 的 ValidatorFactory/Validator 存取能力和 Spring 的 Validator 介面。它參與 Spring 的生命週期管理,並在關閉時關閉底層工廠。

MVC 繫結可以透過 @Valid 或 @Validated 套用限制條件註解。方法驗證透過 Spring 的方法驗證基礎架構,將限制條件套用至方法參數和回傳值。驗證不能取代轉換:值必須先以限制條件所作用的型別表示。

主要參考資料 #

評論

在 GitHub 上閱讀或評論(發表評論需要登入)