将驗證視為業務邏輯有其優缺點,Spring提供的驗證(和資料綁定)設計不排除其中任何一種。具體來說,驗證不應與Web層綁定,并且應該易于本地化,并且應該可以插入任何可用的驗證器。考慮到這些問題,Spring提供了一個 Validator
契約,該契約既基本又可以在應用程式的每個層中使用。
資料綁定對于使使用者輸入動态綁定到應用程式的域模型(或用于處理使用者輸入的任何對象)非常有用。Spring提供了恰當地命名為
DataBinder
的功能。 Validator
和 DataBinder
由 validation
包組成,被主要的使用但不僅限于web層。 BeanWrapper
在Spring架構中是一個基本的概念并且在許多地方被使用到。然而,你大概不需要直接地使用 BeanWrapper
。但是,由于這是參考文檔,是以我們認為可能需要一些解釋。我們将在本章中解釋 BeanWrapper
,因為如果你要使用它,那麼在嘗試将資料綁定到對象時最有可能使用它。
Spring的
DataBinder
和低級别 BeanWrapper
兩者使用 PropertyEditorSupport
實作去解析和格式化屬性值。 PropertyEditor
PropertyEditorSupport
類型是JavaBeans規範的一部分并且在這個章節進行解釋。Spring 3開始引入了 core.convert
包,該包提供了正常的類型轉換工具,以及用于格式化UI字段值的進階“ format
”包。你可以将這些包用作 PropertyEditorSupport
實作的更簡單替代方案。這些也會在這個章節讨論。
Spring通過安裝基礎設計和适配Spring的
Validator
契約提供JavaBean校驗。應用程式可以全局一次啟用Bean驗證,像在 JavaBean校驗 中描述一樣,并且僅将其用于所有驗證需求。在Web層中,應用程式可以每個 DataBinder
進一步注冊控制器本地的Spring Validator
執行個體,如配置 DataBinder
中所述,這對于插入自定義驗證邏輯很有用。 3.1 通過使用Spring的校驗接口校驗
Spring提供一個
Validator
接口,你可以使用它校驗對象。當校驗的時候,
Validator
接口通過使用Errors對象工作,是以校驗器可以報告校驗失敗資訊到
Errors
對象。
考慮下面小資料對象例子:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
下面例子通過實作下面
org.springframework.validation.Validator
接口的兩個方法為
Person
類提供校驗行為。
-
:supports(Class)
校驗接口是否支援ClassValidator
-
: 驗證給定的對象,并在發生驗證錯誤的情況下,使用給定的validate(Object, org.springframework.validation.Errors)
對象注冊這些對象。Errors
實作
Validator
非常簡單,特别地當你知道Spring架構提供的
ValidationUtils
幫助類時。下面例子為
Person
接口實作
Validator
:
public class PersonValidator implements Validator {
/**
* This Validator validates only Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
ValidationUtils
類上的靜态
rejectIfEmpty(...)
方法用于拒絕
name
屬性(如果該屬性為
null
或空字元串)。檢視
ValidationUtils
javadoc,看看它除了提供前面顯示的示例外還提供什麼功能。
雖然可以實作單個驗證器類來驗證對象中的每個嵌套對象,但更好的做法是将每個嵌套對象類的驗證邏輯封裝到自己的驗證器實作中。一個“
豐富
”對象的簡單示例是一個由兩個String屬性(第一個和第二個名字)和一個複雜的
Address
對象組成的
Customer
。
Address
對象可以獨立于
Customer
對象使用,是以已經實作了獨特的
AddressValidator
。如果希望
CustomerValidator
重用
AddressValidator
類中包含的邏輯而需要複制和粘貼,則可以在
CustomerValidator
中依賴注入或執行個體化一個
AddressValidator
,如以下示例所示:
public class CustomerValidator implements Validator {
private final Validator addressValidator;
public CustomerValidator(Validator addressValidator) {
if (addressValidator == null) {
throw new IllegalArgumentException("The supplied [Validator] is " +
"required and must not be null.");
}
if (!addressValidator.supports(Address.class)) {
throw new IllegalArgumentException("The supplied [Validator] must " +
"support the validation of [Address] instances.");
}
this.addressValidator = addressValidator;
}
/**
* This Validator validates Customer instances, and any subclasses of Customer too
*/
public boolean supports(Class clazz) {
return Customer.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
Customer customer = (Customer) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
驗證錯誤将報告給傳遞給驗證器的
Errors
對象。在Spring Web MVC場景中,你可以使用
<spring:bind/>
标簽去檢查錯誤資訊,但是你也可以自己檢查
Errors
對象。更多關于提供的資訊在
Javadoc中。
參考代碼: com.liyong.ioccontainer.service.validator.ValidatorTest
3.2 解析碼到錯誤資訊
我們介紹了資料綁定和校驗。本節介紹與驗證錯誤對應的輸出消息。在上一節顯示的例子中,我們拒絕
name
age
字段。如果我們想使用
MessageSource
去輸出錯誤資訊,我們可以使用提供的錯誤碼,當拒絕字段時(在這個場景中
name
age
)。當你
Errors
接口調用(直接地或間接地,通過使用
ValidationUtils
類)
rejectValue
或其他
reject
方法之一時,底層的實作不僅注冊你傳遞的碼,而且還注冊一些附加的錯誤碼。
MessageCodesResolver
确定哪一個錯誤碼注冊到
Errors
接口。預設情況下,使用
DefaultMessageCodesResolver
,它(例如)不僅使用你提供的代碼注冊消息,而且還注冊包含傳遞給拒絕方法的字段名稱的消息。是以,如果你通過使用
rejectValue(“age”,“too.darn.old”)
拒絕字段,則除了
too.darn.old
代碼外,Spring還将注冊
too.darn.old.age
too.darn.old.age.int
(第一個包含字段名稱,第二個包含字段類型)。這樣做是為了友善開發人員在定位錯誤消息時提供幫助。
更多
MessageCodesResolver
上和預設政策資訊可以分别地在
MessageCodesResolver
DefaultMessageCodesResolver
javadoc中找到。
3.3 bean操作和BeanWrapper
這個
org.springframework.beans
包遵循JavaBeans标準。JavaBean是具有預設無參數構造函數的類,并且遵循命名約定,在該命名約定下,例如:名為
bingoMadness
的屬性将具有
setter
方法
setBingoMadness(..)
getter
getBingoMadness()
。更多關于JavaBean資訊和規範,檢視
javaBeans在
beans
包中一個非常重要的類是
BeanWrapper
接口和它的對應實作(
BeanWrapperImpl
)。就像從Javadoc引言的那樣,
BeanWrapper
提供了以下功能:設定和擷取屬性值(單獨或批量),擷取屬性描述符以及查詢屬性以确定它們是否可讀或可寫。此外,
BeanWrapper
還支援嵌套屬性,進而可以将子屬性上的屬性設定為無限深度。
BeanWrapper
還支援添加标準JavaBeans 的
PropertyChangeListeners
VetoableChangeListeners
的功能,而無需在目标類中支援代碼。最後但并非不重要的一點是,
BeanWrapper
支援設定索引屬性。
BeanWrapper
通常不直接由應用程式代碼使用,而是由
DataBinder
BeanFactory
使用。
BeanWrapper
的工作方式部分由其名稱表示:它包裝一個Bean,以對該Bean執行操作,例如設定和檢索屬性。
3.3.1 設定和擷取基本的和潛入的屬性
設定和擷取屬性是通過
BeanWrapper
的重載方法
setPropertyValue
getPropertyValue
的變體。檢視它們的詳細文檔。下面的表格顯示這些約定:
Expression | Explanation |
---|---|
| 表示屬性 對應的 或 方法。 |
| 表示嵌入 屬性的 屬性對應的 |
| 表示2個索引元素屬性 。索引屬性可以是類型 、 或其他自然順序集合。 |
| 表示 實體的值通過 Map屬性的key 索引。 |
(如果你沒打算直接使用
BeanWrapper
,下面部分不是至關重要地。如果你僅僅使用
DataBinder
BeanFactory
和他的預設實作,你可以跳過
PropertyEditors的部分)。
下面兩個例子類使用
BeanWrapper
去擷取和設定屬性:
public class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
public class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
以下代碼段顯示了一些有關如何檢索和操縱執行個體化的
Company
Employee
的某些屬性的示例:
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
代碼示例: com.liyong.ioccontainer.service.beanwrapper.BeanWrapperTest
3.3.2 内建
PropertyEditor
Spring使用
PropertyEditor
概念去影響一個對象和字元串之間的轉換。以不同于對象本身的方式表示屬性可能很友善。例如,日期可以用人類可讀的方式表示(如字元串:'
2007-14-09
'),而我們仍然可以将人類可讀的形式轉換回原始日期(或者更好的是,轉換任何日期以人類可讀的形式輸入到
Date
對象)。通過注冊類型為
java.beans.PropertyEditor
的自定義編輯器,可以實作此行為。在
BeanWrapper
上或在特定的IoC容器中注冊自定義編輯器(如上一章所述),使它具有如何将屬性轉換為所需類型的能力。更多關于
PropertyEditor
,
請參閱Oracle的java.beans包的javadoc在Spring中使用屬性編輯的兩個示例:
- 通過使用
實作在bean上設定屬性。當使用String作為在XML檔案中聲明的某個bean的屬性的值時,Spring(如果相應屬性的PropertyEditor
具有setter
參數)将使用Class
嘗試将參數解析為ClassEditor
Class
- 在Spring的MVC架構中,通過使用各種
實作來解析HTTP請求參數,你可以在PropertyEditor
的所有子類中手動綁定這些實作。CommandController
Spring有一個内建的
PropertyEditor
實作。它們都位于
org.springframework.beans.propertyeditors
包中。預設情況下,大多數(但不是全部,如下表所示)由
BeanWrapperImpl
注冊。如果可以通過某種方式配置屬性編輯器,則仍可以注冊自己的變體以覆寫預設變體。
下表描述了Spring提供的各種
PropertyEditor
實作:
Class | |
---|---|
| 位元組數組的編輯器。将字元串轉換為其相應的位元組表示形式。預設 注冊。 |
| 将代表類的字元串解析為實際類,反之亦然。當類沒有找到抛出 。預設 |
| 屬性的可定制屬性編輯器。預設,通過 注冊,但是可以通過将其自定義執行個體注冊為自定義編輯器來覆寫它。 |
| 集合屬性編輯器,轉換任何源 到給定 類型。 |
| 的可自定義屬性編輯器,支援一個自定義 。預設不會被注冊。必須根據需要以适當的格式進行使用者注冊。 |
| 任何 子類可自定義屬性編輯器,例如 。預設,通過 |
| 解析字元串為 對象。預設,通過 |
| 單向屬性編輯器,它可以采用字元串并生成(通過中間的 )一個 ,以便可以将 屬性直接設定為字元串。請注意,預設用法不會為你關閉 。預設情況下,由 注冊 |
| 可以将字元串解析為 對象,反之亦然(字元串格式為 ,類似 的toString()方法相同)。預設,通過 |
| 能夠解析字元串為 對象,反之亦然。 |
| 可以将字元串(格式設定為 類的javadoc中定義的格式)轉換為 對象 |
| 修剪字元串的屬性編輯器。 (可選)允許将空字元串轉換為空值。預設不被注冊-必須被使用者注冊。 |
| 能夠轉換一個字元串代表的URL為真實的URL對象。預設,通過 |
java.beans.PropertyEditorManager
去設定屬性編輯器可能需要的搜尋路徑。搜尋路徑也可以包含
sun.bean.editors
,它包括例如
Font
Color
和大多數原始類型的
PropertyEditor
實作。還要注意,如果标準JavaBeans基礎結構與它們處理的類在同一包中并且與該類具有相同的名稱,并且附加了
Editor
,則标準JavaBeans基礎結構會自動發現
PropertyEditor
類(無需顯式注冊它們)。例如,可以使用以下類和包結構,這就足以識别
SomethingEditor
類并将其用作某種類型屬性的
PropertyEditor
com
chank
pop
Something
SomethingEditor // SomethingEditor用作Something類
注意,你也可以在此處使用标準的
BeanInfo
JavaBeans機制(
這裡有所描述)。下面例子使用
BeanInfo
機制去明确地注冊一個或多個
PropertyEditor
執行個體到關聯類的屬性:
com
chank
pop
Something
SomethingBeanInfo // BeanInfo用作Something類
下面是引用的
SomethingBeanInfo
類的Java源代碼,它将
CustomNumberEditor
與
Something
類的
age
屬性關聯起來:
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
com.liyong.ioccontainer.service.propertyeditor.PropertyEditorTest
注冊附加的自定義
PropertyEditor
當設定bean屬性為字元串值時,Spring IoC容器最終地使用标準JavaBean的
PropertyEditor
實作去轉換這些字元串為屬性的複雜類型。Spring預注冊了非常多的自定義
PropertyEditor
實作(例如,将表示為字元串的類名稱轉換為Class對象)。此外,Java的标準JavaBeans
PropertyEditor
查找機制允許适當地命名類的
PropertyEditor
,并将其與提供支援的類放在同一包中,以便可以自動找到它。
如果需要注冊其他自定義
PropertyEditors
,則可以使用幾種機制。最手動的方法(通常不友善或不建議使用)是使用
ConfigurableBeanFactory
接口的
registerCustomEditor()
方法,假設你有
BeanFactory
引用。另一種(稍微友善些)的機制是使用稱為
CustomEditorConfigurer
的特殊bean工廠後處理器。盡管你可以将Bean工廠後處理器與
BeanFactory
實作一起使用,但
CustomEditorConfigurer
具有嵌套的屬性設定,是以我們強烈建議你将其與
ApplicationContext
一起使用,在這裡可以将其以與其他任何Bean相似的方式進行部署,并且可以在任何位置進行部署。自動檢測并應用。
請注意,所有的bean工廠和應用程式上下文通過使用
BeanWrapper
來處理屬性轉換,都會自動使用許多内置的屬性編輯器。上一節列出了
BeanWrapper
注冊的标準屬性編輯器。此外,
ApplicationContext
還以适合特定應用程式上下文類型的方式重寫或添加其他編輯器,以處理資源查找。
标準JavaBeans
PropertyEditor
執行個體用于将表示為字元串的屬性值轉換為屬性的實際複雜類型。你可以使用bean工廠的後處理器
CustomEditorConfigurer
來友善地将對其他
PropertyEditor
執行個體的支援添加到
ApplicationContext
考慮以下示例,該示例定義了一個名為
ExoticType
的使用者類和另一個名為
DependsOnExoticType
的類,該類需要将
ExoticType
設定為屬性:
package example;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}
正确設定之後,我們希望能夠将
type
屬性配置設定為字元串,
PropertyEditor
會将其轉換為實際的
ExoticType
執行個體。以下bean定義顯示了如何建立這種關系:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
PropertyEditor實作可能類似于以下内容:
// converts string representation to ExoticType object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
最後,下面的示例示範如何使用
CustomEditorConfigurer
向
ApplicationContext
注冊新的
PropertyEditor
,然後可以根據需要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
com.liyong.ioccontainer.starter.PropertyEditorIocContainer
使用PropertyEditorRegistrar
在Spring容器中注冊屬性編輯器的其他機制是建立和使用
PropertyEditorRegistrar
。當需要在幾種不同情況下使用同一組屬性編輯器時,此接口特别有用。你可以在每一種場景中寫對應的注冊和重新使用。
PropertyEditorRegistrar
執行個體與一個名為
PropertyEditorRegistry
的接口一起工作,該接口由Spring
BeanWrapper
(和
DataBinder
)實作。與
CustomEditorConfigurer
(在此描述)結合使用時,
PropertyEditorRegistrar
執行個體特别友善,該執行個體暴露了名為
setPropertyEditorRegistrars(..)
的屬性。以這種方式添加到
CustomEditorConfigurer
中的
PropertyEditorRegistrar
執行個體可以輕松地與
DataBinder
和Spring MVC控制器共享。此外,它避免了在自定義編輯器上進行同步的需求:希望
PropertyEditorRegistrar
為每次建立bean的嘗試建立新的
PropertyEditor
執行個體。
以下示例說明如何建立自己的
PropertyEditorRegistrar
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 期望建立一個新的PropertyEditor示例
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}
另請參閱
org.springframework.beans.support.ResourceEditorRegistrar
以擷取示例
PropertyEditorRegistrar
實作。注意,在實作
registerCustomEditors(...)
方法時,它如何建立每個屬性編輯器的新執行個體。
下一個示例顯示了如何配置
CustomEditorConfigurer
并将其注入我們的
CustomPropertyEditorRegistrar
的執行個體:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最後(對于使用Spring的MVC Web架構的讀者來說,與本章的重點有所偏離),使用
PropertyEditorRegistrars
與資料綁定
Controllers
(例如
SimpleFormController
)結合使用會非常友善。下面的示例在
initBinder(..)
方法的實作中使用
PropertyEditorRegistrar
public final class RegisterUserController extends SimpleFormController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods to do with registering a User
}
這種
PropertyEditor
注冊樣式可以使代碼簡潔(
initBinder(..)
的實作隻有一行長),并且可以将通用的
PropertyEditor
注冊代碼封裝在一個類中,然後根據需要在許多
Controller
之間共享。
3.4 Spring類型轉換
Spring 3 已經引入一個
core.convert
包,它提供了一般類型系統轉換。系統定義了一個用于實作類型轉換邏輯的SPI和一個用于在運作時執行類型轉換的API。在Spring容器中,可以使用此特性作為
PropertyEditor
實作的替代方法,以将外部化的bean屬性值字元串轉換為所需的屬性類型。你還可以在應用程式中需要類型轉換的任何地方使用公共API。
3.4.1 轉換SPI
如以下接口定義所示,用于實作類型轉換邏輯的SPI非常簡單且具有強類型:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
要建立自己的轉換器,請實作
Converter
接口,并将
S
設定為要被轉換的類型,并将
T
設定為要轉換為的類型。如果需要将
S
的集合或數組轉換為
T
的集合,并且已經注冊了委托數組或集合轉換器(預設情況下,
DefaultConversionService
會這樣做),那麼你還可以透明地應用這樣的轉換器。
對于每次
convert(S)
的調用,方法參數必須保證不能為
null
。如果轉換失敗,你的
Converter
可能抛出未檢查異常。特别地,它可能抛出
IllegalArgumentException
去報告無效參數值異常。小心的去確定
Converter
實作是線程安全的。
為了友善起見,在
core.convert.support
包中提供了幾種轉換器實作。這些包括從字元串到數字和其他常見類型的轉換器。下面的清單顯示了
StringToInteger
類,它是一個典型的
Converter
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
3.4.2 使用
ConverterFactory
當需要集中整個類層次結構的轉換邏輯時(例如,從
String
轉換為
Enum
對象時),可以實作
ConverterFactory
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
參數化
S
為你要轉換的類型,參數
R
為基礎類型,定義可以轉換為的類的範圍。然後實作
getConverter(Class <T>)
,其中
T
是
R
的子類。
考慮
StringToEnumConverterFactory
例子:
package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
3.4.3 使用
GenericConverter
當你需要複雜的
Converter
實作時,請考慮使用
GenericConverter
接口。與
Converter
相比,
GenericConverter
具有比
Converter
更靈活但類型不強的簽名,支援多種源類型和目标類型之間進行轉換。此外,
GenericConverter
還提供了在實作轉換邏輯時可以使用的源和目标字段上下文。這樣的上下文允許由字段注解或在字段簽名上聲明的泛型資訊驅動類型轉換。下面清單顯示
GenericConverter
接口定義:
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
GenericConverter
,需要
getConvertibleTypes()
傳回支援的源→目标類型對。然後實作
convert(Object, TypeDescriptor, TypeDescriptor)
去包含你的轉換邏輯。源
TypeDescriptor
提供對包含正在轉換的值的源字段的通路。使用目标
TypeDescriptor
,可以通路要設定轉換值的目标字段。
GenericConverter
的一個很好的例子是在Java數組和集合之間進行轉換的轉換器。這樣的
ArrayToCollectionConverter
會檢查聲明目标集合類型的字段以解析集合的元素類型。這樣就可以在将集合設定到目标字段上之前,将源數組中的每個元素轉換為集合元素類型。
由于是一個更複雜的SPI接口,是以僅應在需要時使用它。支援
GenericConverter
Converter
以滿足基本的類型轉換需求。
ConverterFactory
com.liyong.ioccontainer.service.converter.GenericConverterTest
使用
ConditionalGenericConverter
有時,你希望
Converter
僅在滿足特定條件時才運作。例如,你可能隻想在目标字段上存在特定注解時才運作
Converter
,或者可能在目标類上定義了特定方法(例如靜态
valueOf
方法)時才運作
Converter
ConditionalGenericConverter
GenericConverter
ConditionalConverter
接口的聯合,可讓你定義以下自定義比對條件:
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
ConditionalGenericConverter
的一個很好的例子是
EntityConverter
,它在持久實體辨別和實體引用之間轉換。僅當目标實體類型聲明靜态查找器方法(例如
findAccount(Long)
)時,此類
EntityConverter
才可能比對。你可以在
matchs(TypeDescriptor,TypeDescriptor)
的實作中執行這種
finder
方法檢查。
com.liyong.ioccontainer.service.converter.ConditionalConverterTest
3.4.4
ConversionService
API
ConversionService
定義了一個統一的API,用于在運作時執行類型轉換邏輯。轉換器通常在以下門面接口執行:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
大多數
ConversionService
實作也都實作
ConverterRegistry
,該轉換器提供用于注冊轉換器的SPI。在内部,
ConversionService
實作委派其注冊的轉換器執行類型轉換邏輯。
core.convert.support
包中提供了一個強大的
ConversionService
實作。
GenericConversionService
是适用于大多數環境的通用實作。
ConversionServiceFactory
提供了一個友善的工廠來建立通用的ConversionService配置。
3.4.5 配置 ConversionService
ConversionService
ConversionService
是無狀态對象,旨在在應用程式啟動時執行個體化,然後在多個線程之間共享。在Spring應用程式中,通常為每個Spring容器(或
ApplicationContext
)配置一個
ConversionService
執行個體。當架構需要執行類型轉換時,Spring會使用該
ConversionService
并使用它。你還可以将此
ConversionService
注入到任何bean中,然後直接調用它。
如果沒有向Spring注冊,則使用原始的基于
ConversionService
的特性。
propertyeditor
要向Spring注冊預設的
ConversionService
,請添加以下bean定義,其id為
conversionService
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
預設的
ConversionService
可以在字元串、數字、枚舉、集合、映射和其他常見類型之間進行轉換。要用你自己的自定義轉換器補充或覆寫預設轉換器,請設定
converters
屬性。屬性值可以實作
Converter
ConverterFactory
GenericConverter
接口中的任何一個。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
在Spring MVC應用程式中使用
ConversionService
也很常見。參見Spring MVC一章中的
轉換和格式化在某些情況下,你可能希望在轉換過程中應用格式設定。有關使用
FormattingConversionServiceFactoryBean
的詳細資訊,請參見
FormatterRegistry SPI3.4.6 程式設計式地使用
ConversionService
要以程式設計方式使用
ConversionService
執行個體,可以像對其他任何bean一樣注入對該bean例的引用。以下示例顯示了如何執行此操作:
@Service
public class MyService {
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
對于大多數用例,可以使用指定
targetType
的
convert
方法,但不适用于更複雜的類型,例如參數化元素的集合。例如,如果要以程式設計方式将整數清單轉換為字元串清單,則需要提供源類型和目标類型的格式定義。
幸運的是,如下面的示例所示,
TypeDescriptor
提供了各種選項來使操作變得簡單明了:
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
請注意,
DefaultConversionService
自動注冊适用于大多數環境的轉換器。這包括集合轉換器、标量轉換器和基本的對象到字元串轉換器。你可以使用
DefaultConversionService
addDefaultConverters
方法向任何
ConverterRegistry
注冊相同的轉換器。
值類型的轉換器可重用于數組和集合,是以,假設标準集合處理适當,則無需建立特定的轉換器即可将
S
的集合轉換為
T
的集合。
3.5 Spring字段格式
如上一節所述,
core.convert
是一種通用類型轉換系統。它提供了統一的
ConversionService
API和強類型的
Converter
SPI,用于實作從一種類型到另一種類型的轉換邏輯。Spring容器使用此系統綁定bean屬性值。此外,Spring Expression Language(SpEL)和
DataBinder
都使用此系統綁定字段值。例如,當SpEL需要強制将
Short
Long
來完成
expression.setValue(Object bean,Object value)
嘗試時,
core.convert
系統将執行強制轉換。
考慮一個典型的用戶端環境轉換需求,例如web或桌面應用。在這種環境中,你通常将字元串轉換為支援用戶端送出處理,以及将字元串轉換為支援視圖呈現過程。以及,你通常需要本地化
String
值。更通用的
core.convert
Converter
SPI不能直接滿足此類格式化要求。為了直接解決這些問題,Spring 3 引入了友善的
Formatter
SPI,它為用戶端環境提供了
PropertyEditor
實作的簡單而強大的替代方案。
通常,當你需要實作通用類型轉換邏輯時,可以使用
Converter
SPI,例如,在
java.util.Date
Long
之間轉換。當你在用戶端環境中(例如,web應用)并且需要去解析和列印本地化字段值時,你可以使用
Formatter
SPI。
ConversionService
為這兩個SPI提供統一的類型轉換。
3.5.1
Formatter
SPI
Formatter
SPI去實作字段格式邏輯是簡單和強類型的。下面清單顯示
Formatter
接口資訊:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter
從
Printer
Parser
建構塊接口拓展。下面清單顯示這兩個接口定義:
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}
import java.text.ParseException;
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}
去建立你自己的
Formatter
,實作前面展示的
Formatter
接口。将
T
參數化為你希望格式化的對象類型-例如,
java.util.Date
。實作
print()
操作以列印
T
的執行個體以在用戶端語言環境中顯示。實作
parse()
操作,以從用戶端本地傳回的格式化表示形式解析
T
的執行個體。如果嘗試解析失敗,你的
Formatter
應該抛一個
ParseException
IllegalArgumentException
異常。注意確定你的
Formatter
為了友善
format
子包提供一些
Formatter
number
包提供
NumberStyleFormatter
CurrencyStyleFormatter
PercentStyleFormatter
去格式化
Number
對象,它使用
java.text.NumberFormat
datetime
DateFormatter
java.util.Date
java.text.DateFormat
datetime.joda
包基于
Joda-Time庫提供了全面的日期時間格式支援。
下面
DateFormatter
Formatter
實作例子:
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
private String pattern;
public DateFormatter(String pattern) {
this.pattern = pattern;
}
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
dateFormat.setLenient(false);
return dateFormat;
}
}
Spring歡迎社群驅動
Formatter
貢獻。檢視
GitHub Issues去貢獻。
3.5.2 注解驅動格式
可以通過字段類型或注解配置字段格式。要将注解綁定到
Formatter
,請實作
AnnotationFormatterFactory
。下面清單顯示
AnnotationFormatterFactory
package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
去建立一個實作:将
A
參數化為要與格式邏輯關聯的字段
annotationType
,例如,
org.springframework.format.annotation.DateTimeFormat
讓
getFieldTypes()
傳回可在其上使用注解的字段類型。讓
getPrinter()
傳回
Printer
以列印帶注解的字段的值。讓
getParser()
Parser
去為注解字段解析
clientValue
下面的示例
AnnotationFormatterFactory
實作将
@NumberFormat
注解綁定到格式化程式,以指定數字樣式或模式:
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
Short.class, Integer.class, Long.class, Float.class,
Double.class, BigDecimal.class, BigInteger.class }));
}
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
if (!annotation.pattern().isEmpty()) {
return new NumberStyleFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentStyleFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
} else {
return new NumberStyleFormatter();
}
}
}
}
觸發格式,可以使用
@NumberFormat
注解字段,如以下示例所示:
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
格式注解API
org.springframework.format.annotation
包中存在一個可移植的格式注解API。你可以使用
@NumberFormat
格式化
Number
字段(例如
Double
Long
),并使用
@DateTimeFormat
java.util.Date
java.util.Calendar
Long
(用于毫秒時間戳)以及JSR-310
java.time
Joda-Time
值類型。
下面例子使用
@DateTimeFormat
去格式
java.util.Date
為ISO日期(
yyyy-MM-dd
);
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
3.5.3
FormatterRegistry
FormatterRegistry
是一個SPI用于注冊格式化器和轉換器。
FormattingConversionService
FormatterRegistry
實作适用于絕大環境。通過使用
FormattingConversionServiceFactoryBean
,你可以程式設計式地或聲明式配置這些變體作為Spring bean。由于此實作還實作了
ConversionService
,是以你可以直接将其配置為與Spring的
DataBinder
和Spring表達式語言(SpEL)一起使用。
下面清單顯示
FormatterRegistry
SPI接口定義:
package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Formatter<?> formatter);
void addFormatterForAnnotation(AnnotationFormatterFactory<?> factory);
}
像在前面清單顯示,你通過字段類型或通過注解注冊格式化器。
FormatterRegistry
SPI使你可以集中配置格式設定規則,而不必在控制器之間重複此類配置。例如,你可能要強制所有日期字段以某種方式設定格式或帶有特定注解的字段以某種方式設定格式。使用共享的
FormatterRegistry
,你可以一次定義這些規則,并在需要格式化時應用它們。
3.5.4
FormatterRegistrar
FormatterRegistrar
是一個SPI,用于通過
FormatterRegistry
注冊格式器和轉換器。以下清單顯示了其接口定義:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
為給定的格式類别(例如日期格式)注冊多個相關的轉換器和格式器時,
FormatterRegistrar
很有用。在聲明式注冊不充分的情況下它也很有用。例如,當格式化程式需要在不同于其自身的特定字段類型下進行索引時,或者在注冊
Printer
/
Parser
對時。下一節将提供有關轉換器和格式化注冊的更多資訊。
3.5.5 在Spring MVC中配置格式化
在Spring MVC章節中,檢視
Conversion 和 Formatting 3.6 配置全局 Date
Time
格式
Date
Time
預設情況下,未使用
@DateTimeFormat
注解日期和時間字段是使用
DateFormat.SHORT
格式從字元串轉換的。如果願意,可以通過定義自己的全局格式來更改此設定。
為此,請確定Spring不注冊預設格式器。相反,可以借助以下方法手動注冊格式化器:
-
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
-
或為org.springframework.format.datetime.DateFormatterRegistrar
Joda-Time
org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar
例如,下面Java配置注冊一個全局的
yyyyMMdd
格式:
@Configuration
public class AppConfig {
@Bean
public FormattingConversionService conversionService() {
// Use the DefaultFormattingConversionService but do not register defaults
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
// Ensure @NumberFormat is still supported
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
registrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
registrar.setFormatter(new DateFormatter("yyyyMMdd"));
registrar.registerFormatters(conversionService);
return conversionService;
}
}
如果你偏好與基于XML配置,你可以使用
FormattingConversionServiceFactoryBean
。下面例子顯示怎樣去做(這裡使用
Joda Time
):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="registerDefaultFormatters" value="false" />
<property name="formatters">
<set>
<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar">
<property name="dateFormatter">
<bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean">
<property name="pattern" value="yyyyMMdd"/>
</bean>
</property>
</bean>
</set>
</property>
</bean>
</beans>
注意:當在web應用中配置日期和時間格式時需要額外考慮。請檢視
WebMVC Conversion 和 Formattingor
WebFlux Conversion 和 Formatting.
3.7 Java Bean校驗
Spring架構提供對J
ava Bean校驗API。
3.7.1 Bean校驗概要
Bean驗證為Java應用程式提供了通過限制聲明和中繼資料進行驗證的通用方法。要使用它,你需要使用聲明性驗證限制對域模型屬性進行注解,然後由通過運作時強制實施限制。有内置的限制,你也可以定義自己的自定義限制。
考慮以下示例,該示例顯示了具有兩個屬性的簡單
PersonForm
模型:
public class PersonForm {
private String name;
private int age;
}
Bean驗證使你可以聲明限制,如以下示例所示:
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
然後,Bean驗證器根據聲明的限制來驗證此類的執行個體。有關該API的一般資訊,請參見
Bean Validation。有關特定限制,請參見
Hibernate Validator文檔。要學習如何将bean驗證提供程式設定為Spring bean,請繼續閱讀。
3.7.2 配置Bean Validation提供者
Spring提供了對Bean驗證API的全面支援,包括将Bean驗證提供程式作為Spring Bean執行引導。這使你可以在應用程式中需要驗證的任何地方注入
javax.validation.ValidatorFactory
javax.validation.Validator
你可以使用
LocalValidatorFactoryBean
将預設的
Validator
配置為Spring Bean,如以下示例所示:
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean;
}
}
前面示例中的基本配置觸發Bean驗證以使用其預設引導機制進行初始化。Bean驗證提供程式,例如
Hibernate
Validator
,應該存在于類路徑中并被自動檢測到。
注入校驗器
LocalValidatorFactoryBean
同時實作
javax.validation.ValidatorFactory
javax.validation.Validator
以及Spring的
org.springframework.validation.Validator
。你可以将對這些接口之一的引用注入需要調用驗證邏輯的bean中。
如果你希望直接使用Bean
Validation
API,則可以注入對
javax.validation.Validator
的引用,如以下示例所示:
import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
配置自定義限制
每個bean校驗限制由兩部分組成:
-
注解,用于聲明限制及其可配置屬性。@Constraint
-
接口的實作,用于實作限制的行為。javax.validation.ConstraintValidator
要将聲明與實作相關聯,每個
@Constraint
注解都引用一個對應的
ConstraintValidator
實作類。在運作時,當在域模型中遇到限制注解時,
ConstraintValidatorFactory
執行個體化引用的實作。
預設情況下,
LocalValidatorFactoryBean
配置一個
SpringConstraintValidatorFactory
,該工廠使用Spring建立
ConstraintValidator
執行個體。這使你的自定義
ConstraintValidators
像其他任何Spring bean一樣受益于依賴項注入。
以下示例顯示了一個自定義
@Constraint
聲明,後跟一個關聯的
ConstraintValidator
實作,該實作使用Spring進行依賴項注入:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
@Autowired;
private Foo aDependency;
// ...
}
如前面的示例所示,
ConstraintValidator
實作可以像其他任何Spring bean一樣具有
@Autowired
依賴項。
com.liyong.ioccontainer.service.validator.ConstraintTest
Spring驅動方法驗證
你可以通過
MethodValidationPostProcessor
bean定義将
Bean Validation 1.1
(以及作為自定義擴充,還包括
Hibernate
Validator 4.3
)支援的方法驗證功能內建到Spring上下文中:
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor;
}
}
為了有資格進行Spring驅動的方法驗證,所有目标類都必須使用Spring的
@Validated
注解進行注釋,該注解也可以選擇聲明要使用的驗證組。有關使用
Hibernate Validator
Bean Validation 1.1
提供程式的設定詳細資訊,請參見
MethodValidationPostProcessor 方法驗證依賴于目标類周圍的AOP代理,即接口上方法的JDK動态代理或CGLIB代理。代理的使用存在某些限制,在 了解 AOP 代理 中介紹了其中的一些限制。另外,請記住在代理類上使用方法和通路器;直接通路将不起作用。 com.liyong.ioccontainer.starter.MethodvalidationIocContainer
其他配置選項
在大多數情況下,預設
LocalValidatorFactoryBean
配置就足夠了。從消息插值到周遊解析,有多種用于各種Bean驗證構造的配置選項。有關這些選項的更多資訊,請參見
LocalValidatorFactoryBeanJavadoc。
3.7.3 配置
DataBinder
從Spring 3 開始,你可以使用
Validator
配置
DataBinder
執行個體。配置完成後,你可以通過調用
binder.validate()
來調用
Validator
。任何驗證錯誤都會自動添加到綁定的
BindingResult
下面的示例示範如何在綁定到目标對象後,以程式設計方式使用
DataBinder
來調用驗證邏輯:
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
你還可以通過
dataBinder.addValidators
dataBinder.replaceValidators
配置具有多個
Validator
執行個體的
DataBinder
。當将全局配置的bean驗證與在
DataBinder
執行個體上本地配置的Spring
Validator
結合使用時,這很有用。檢視
Spring MVC 校驗配置 com.liyong.ioccontainer.service.validator.ValidatorTest
3.7.4 Spring MVC 3 校驗
在Sprint MVC 章節中,檢視
Validation作者
個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職于某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料存儲、自動化內建和部署、分布式微服務、響應式程式設計、人工智能等領域。同時也熱衷于技術分享創立公衆号和部落格站點對知識體系進行分享。關注公衆号:青年IT男 擷取最新技術文章推送!
部落格位址:
http://youngitman.techCSDN:
https://blog.csdn.net/liyong1028826685微信公衆号:
技術交流群: