Spring 將轉換、呈現格式化、物件繫結和驗證視為彼此獨立的操作。因此,要求參數可以轉換為具型別的屬性,同時仍能產生結構化錯誤,而不必將領域物件與 Web 控制器耦合。
轉換與格式化 #
ConversionService 是型別轉換的外觀。它可以先判斷是否有可用的轉換,然後依原始類別或 TypeDescriptor 執行轉換;後者會保留註解和巢狀泛型資訊。
擴充介面分別涵蓋不同需求:
Converter<S, T>實作一次強型別轉換。ConverterFactory<S, R>為一組目標子型別建立轉換器,例如將String轉換為任意列舉型別。GenericConverter宣告可轉換的來源/目標型別配對集合,並接收TypeDescriptor值,以執行依賴內容的轉換。ConditionalGenericConverter也可以判斷自身是否符合特定的來源與目標。
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 中,並且可以呼叫驗證器。
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 回報失敗:
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 上閱讀或評論(發表評論需要登入)