將驗證視爲業務邏輯有其優缺點,Spring提供的驗證(和數據綁定)設計不排除其中任何一種。具體來講,驗證不該與Web層綁定,而且應該易於本地化,而且應該能夠插入任何可用的驗證器。考慮到這些問題,Spring提供了一個Validator
契約,該契約既基本又能夠在應用程序的每一個層中使用。html
數據綁定對於使用戶輸入動態綁定到應用程序的域模型(或用於處理用戶輸入的任何對象)很是有用。Spring提供了恰當地命名爲DataBinder
的功能。Validator
和DataBinder
由validation
包組成,被主要的使用但不只限於web層。java
BeanWrapper
在Spring框架中是一個基本的概念而且在許多地方被使用到。然而,你大概不須要直接地使用BeanWrapper
。可是,因爲這是參考文檔,因此咱們認爲可能須要一些解釋。咱們將在本章中解釋BeanWrapper
,由於若是你要使用它,那麼在嘗試將數據綁定到對象時最有可能使用它。react
Spring的DataBinder
和低級別BeanWrapper
二者使用PropertyEditorSupport
實現去解析和格式化屬性值。PropertyEditor
和PropertyEditorSupport
類型是JavaBeans規範的一部分而且在這個章節進行解釋。Spring 3開始引入了core.convert
包,該包提供了常規的類型轉換工具,以及用於格式化UI字段值的高級「 format
」包。你能夠將這些包用做PropertyEditorSupport
實現的更簡單替代方案。這些也會在這個章節討論。git
Spring經過安裝基礎設計和適配Spring的Validator
契約提供JavaBean校驗。應用程序能夠全局一次啓用Bean驗證,像在JavaBean校驗中描述同樣,而且僅將其用於全部驗證需求。在Web層中,應用程序能夠每一個DataBinder
進一步註冊控制器本地的Spring Validator
實例,如配置DataBinder
中所述,這對於插入自定義驗證邏輯頗有用。github
Spring提供一個Validator
接口,你可使用它校驗對象。當校驗的時候,Validator
接口經過使用Errors對象工做,所以校驗器能夠報告校驗失敗信息到Errors
對象。web
考慮下面小數據對象例子:spring
public class Person { private String name; private int age; // the usual getters and setters... }
下面例子經過實現下面org.springframework.validation.Validator
接口的兩個方法爲Person
類提供校驗行爲。express
supports(Class)
: Validator
校驗接口是否支持Classvalidate(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,看看它除了提供前面顯示的示例外還提供什麼功能。api
雖然能夠實現單個驗證器類來驗證對象中的每一個嵌套對象,但更好的作法是將每一個嵌套對象類的驗證邏輯封裝到本身的驗證器實現中。一個「豐富
」對象的簡單示例是一個由兩個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
咱們介紹了數據綁定和校驗。本節介紹與驗證錯誤對應的輸出消息。在上一節顯示的例子中,咱們拒絕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中找到。
這個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執行操做,例如設置和檢索屬性。
設置和獲取屬性是經過BeanWrapper
的重載方法setPropertyValue
和getPropertyValue
的變體。查看它們的詳細文檔。下面的表格顯示這些約定:
Expression | Explanation |
---|---|
name |
表示屬性name 對應的getName() 或 isName() 和 setName(..) 方法。 |
account.name |
表示嵌入account 屬性的name 屬性對應的getAccount().setName() 或getAccount().getName() 方法 |
account[2] |
表示2個索引元素屬性account 。索引屬性能夠是類型array 、list 或其餘天然順序集合。 |
account[COMPANYNAME] |
表示map 實體的值經過account Map屬性的key COMPANYNAME 索引。 |
(若是你沒打算直接使用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
PropertyEditor
實現Spring使用PropertyEditor
概念去影響一個對象和字符串之間的轉換。以不一樣於對象自己的方式表示屬性可能很方便。例如,日期能夠用人類可讀的方式表示(如字符串:'2007-14-09
'),而咱們仍然能夠將人類可讀的形式轉換回原始日期(或者更好的是,轉換任何日期以人類可讀的形式輸入到Date
對象)。經過註冊類型爲java.beans.PropertyEditor
的自定義編輯器,能夠實現此行爲。在BeanWrapper
上或在特定的IoC容器中註冊自定義編輯器(如上一章所述),使它具備如何將屬性轉換爲所需類型的能力。更多關於PropertyEditor
,請參閱Oracle的java.beans包的javadoc。
在Spring中使用屬性編輯的兩個示例:
PropertyEditor
實如今bean上設置屬性。當使用String做爲在XML文件中聲明的某個bean的屬性的值時,Spring(若是相應屬性的setter
具備Class
參數)將使用ClassEditor
嘗試將參數解析爲Class
對象。PropertyEditor
實現來解析HTTP請求參數,你能夠在CommandController
的全部子類中手動綁定這些實現。Spring有一個內建的PropertyEditor
實現。它們都位於org.springframework.beans.propertyeditors
包中。默認狀況下,大多數(但不是所有,以下表所示)由BeanWrapperImpl
註冊。若是能夠經過某種方式配置屬性編輯器,則仍能夠註冊本身的變體以覆蓋默認變體。
下表描述了Spring提供的各類PropertyEditor
實現:
Class | Explanation |
---|---|
ByteArrayPropertyEditor |
字節數組的編輯器。將字符串轉換爲其相應的字節表示形式。默認 BeanWrapperImpl 註冊。 |
ClassEditor |
將表明類的字符串解析爲實際類,反之亦然。當類沒有找到拋出IllegalArgumentException 。默認 BeanWrapperImpl 註冊。 |
CustomBooleanEditor |
Boolean 屬性的可定製屬性編輯器。默認,經過BeanWrapperImpl 註冊,可是能夠經過將其自定義實例註冊爲自定義編輯器來覆蓋它。 |
CustomCollectionEditor |
集合屬性編輯器,轉換任何源Collection 到給定Collection 類型。 |
CustomDateEditor |
java.util.Date 的可自定義屬性編輯器,支持一個自定義DateFormat 。默認不會被註冊。必須根據須要以適當的格式進行用戶註冊。 |
CustomNumberEditor |
任何Number 子類可自定義屬性編輯器,例如Integer 、Long 、Float 或Double 。默認,經過BeanWrapperImpl 註冊,可是能夠經過將其自定義實例註冊爲自定義編輯器來覆蓋它。 |
FileEditor |
解析字符串爲java.io.File 對象。默認,經過BeanWrapperImpl 註冊。 |
InputStreamEditor |
單向屬性編輯器,它能夠採用字符串並生成(經過中間的ResourceEditor 和Resource )一個InputStream ,以即可以將InputStream 屬性直接設置爲字符串。請注意,默認用法不會爲你關閉InputStream 。默認狀況下,由BeanWrapperImpl 註冊 |
LocaleEditor |
能夠將字符串解析爲Locale 對象,反之亦然(字符串格式爲[country] [variant] ,相似Locale 的toString()方法相同)。默認,經過BeanWrapperImpl 註冊 |
PatternEditor |
可以解析字符串爲java.util.regex.Pattern 對象,反之亦然。 |
PropertiesEditor |
能夠將字符串(格式設置爲java.util.Properties 類的javadoc中定義的格式)轉換爲Properties 對象 |
StringTrimmerEditor |
修剪字符串的屬性編輯器。 (可選)容許將空字符串轉換爲空值。默認不被註冊-必須被用戶註冊。 |
URLEditor |
可以轉換一個字符串表明的URL爲真實的URL對象。默認,經過BeanWrapperImpl 註冊。 |
Spring使用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
之間共享。
Spring 3 已經引入一個core.convert
包,它提供了通常類型系統轉換。系統定義了一個用於實現類型轉換邏輯的SPI和一個用於在運行時執行類型轉換的API。在Spring容器中,可使用此特性做爲PropertyEditor
實現的替代方法,以將外部化的bean屬性值字符串轉換爲所需的屬性類型。你還能夠在應用程序中須要類型轉換的任何地方使用公共API。
如如下接口定義所示,用於實現類型轉換邏輯的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); } }
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()); } } }
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
會檢查聲明目標集合類型的字段以解析集合的元素類型。這樣就能夠在將集合設置到目標字段上以前,將源數組中的每一個元素轉換爲集合元素類型。
因爲GenericConverter
是一個更復雜的SPI接口,所以僅應在須要時使用它。支持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
ConversionService
APIConversionService
定義了一個統一的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配置。
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 SPI。
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
的集合。
如上一節所述,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提供統一的類型轉換。
Formatter
SPIFormatter
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去貢獻。
能夠經過字段類型或註解配置字段格式。要將註解綁定到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; }
FormatterRegistry
SPIFormatterRegistry
是一個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
,你能夠一次定義這些規則,並在須要格式化時應用它們。
FormatterRegistrar
SPIFormatterRegistrar
是一個SPI,用於經過FormatterRegistry
註冊格式器和轉換器。如下清單顯示了其接口定義:
package org.springframework.format; public interface FormatterRegistrar { void registerFormatters(FormatterRegistry registry); }
爲給定的格式類別(例如日期格式)註冊多個相關的轉換器和格式器時,FormatterRegistrar
頗有用。在聲明式註冊不充分的狀況下它也頗有用。例如,當格式化程序須要在不一樣於其自身<T>的特定字段類型下進行索引時,或者在註冊Printer
/Parser
對時。下一節將提供有關轉換器和格式化註冊的更多信息。
在Spring MVC章節中,查看 Conversion 和 Formatting 。
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 和 Formatting or WebFlux Conversion 和 Formatting.
Spring框架提供對Java Bean校驗API。
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,請繼續閱讀。
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驗證構造的配置選項。有關這些選項的更多信息,請參見LocalValidatorFactoryBean Javadoc。
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
在Sprint MVC 章節中,查看Validation。
我的從事金融行業,就任過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就任於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公衆號和博客站點對知識體系進行分享。關注公衆號: 青年IT男 獲取最新技術文章推送!
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1...
微信公衆號:
技術交流羣: