天天看點

SpringFramework核心技術三:驗證,資料綁定和類型轉換

驗證,資料綁定和類型轉換

在Spring架構中,驗證,資料綁定和類型轉換都是比較重要的環節,下面我們來一起學習一下

一、介紹

JSR-303 / JSR-349 Bean驗證

Spring Framework 4.0在安裝支援方面支援Bean Validation 1.0(JSR-303)和Bean Validation 1.1(JSR-349),并将其與Spring的Validator接口相适應。

如Spring驗證所述,應用程式可以選擇全局啟用Bean驗證 ,并專門用于所有驗證需求。

應用程式還可以為Validator每個 DataBinder執行個體注冊其他Spring 執行個體,如配置DataBinder中所述。這對于插入驗證邏輯而不使用注釋可能很有用。

将驗證視為業務邏輯有優點和缺點,Spring提供了驗證(和資料綁定)設計,不排除其中任何一個。具體的驗證不應該綁定到Web層,應該易于本地化,應該可以插入任何可用的驗證器。考慮到上述情況,Spring已經提出了一個Validator界面,它在應用程式的每一層都是基本的和顯着的可用的。

資料綁定對于允許使用者輸入動态綁定到應用程式的域模型(或用于處理使用者輸入的任何對象)很有用。Spring提供了所謂的DataBinder完成。在Validator和 DataBinder補validation包,它主要在使用,但不限于MVC架構。

這BeanWrapper是Spring架構中的一個基本概念,并在很多地方使用。但是,您可能不需要BeanWrapper 直接使用。因為這是參考檔案,是以我們覺得有些解釋可能是按順序的。我們将BeanWrapper在本章中解釋這一點,因為如果您打算使用它,那麼在嘗試将資料綁定到對象時很可能會這樣做。

Spring的DataBinder和較低級的BeanWrapper都使用PropertyEditor來解析和格式化屬性值。這個PropertyEditor概念是JavaBeans規範的一部分,本章也對此進行了解釋。Spring 3引入了一個“core.convert”包,它提供了一個通用的類型轉換工具,以及一個用于格式化UI字段值的進階“格式”包。這些新軟體包可以作為PropertyEditor的簡單替代品,本章也将對此進行讨論。

二、使用Spring的Validator接口進行驗證

Spring提供了一個Validator可以用來驗證對象的接口。該 Validator接口使用Errors對象工作,以便在驗證時,驗證器可以向Errors對象報告驗證失敗。

我們來考慮一個小資料對象:

public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}           

我們将Person通過實作

org.springframework.validation.Validator

接口的以下兩種方法來為該類提供驗證行為:

supports(Class)- 這可以Validator驗證提供的執行個體Class嗎?

validate(Object, org.springframework.validation.Errors)- 驗證給定對象,如果出現驗證錯誤,則将其注冊到給定Errors對象           

實作a Validator相當簡單,特别是當你知道ValidationUtilsSpring架構也提供的 helper類的時候。

public class PersonValidator implements Validator {

    /**
     * This Validator validates *just* 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");
        }
    }
}           

正如你所看到的,類中的static rejectIfEmpty(..)方法ValidationUtils用于拒絕’name’屬性,如果它是null或空字元串。看看ValidationUtilsjavadocs,看看它提供的功能,除了前面所示的例子。

盡管可以實作一個Validator類來驗證豐富對象中的每個嵌套對象,但最好在每個嵌套的對象類的自身Validator實作中封裝驗證邏輯。一個“富”對象的簡單例子Customer就是由兩個String 屬性(第一個和第二個名稱)和一個複雜Address對象組成。Address對象可以獨立于Customer對象使用,是以AddressValidator 已經實作了獨特的功能。如果您希望CustomerValidator重複使用AddressValidator類中包含的邏輯而無需複制粘貼,則可以AddressValidator在内部依賴注入或執行個體化CustomerValidator,并使用它:

public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    //将複雜對象的校驗addressValidator注入到CustomerValidator中
    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/>

标簽檢查錯誤消息,但當然您也可以自己檢查錯誤對象。有關它提供的方法的更多資訊可以在javadocs中找到。

三、将代碼解析為錯誤消息

我們已經讨論過資料綁定和驗證。輸出與驗證錯誤相對應的消息是我們需要讨論的最後一件事。在我們上面顯示的例子中,我們拒絕了name該age字段。如果我們要使用a輸出錯誤消息MessageSource,那麼我們将使用我們在拒絕該字段(本例中為’name’和’age’)時給出的錯誤代碼。從接口調用(直接或間接使用ValidationUtils類)rejectValue或其他reject方法時Errors,底層實作不僅會注冊您傳入的代碼,還會附加一些其他錯誤代碼。它注冊的錯誤代碼取決于MessageCodesResolver使用的錯誤代碼。預設情況下,DefaultMessageCodesResolver例如,它不僅用您輸入的代碼注冊消息,而且還會輸入包含您傳遞給拒絕方法的字段名稱的消息。是以,如果您拒絕一個字段 rejectValue(“age”, “too.darn.old”),除了too.darn.old代碼之外,Spring還會注冊too.darn.old.age并且too.darn.old.age.int(是以第一個将包含字段名稱,第二個将包含字段類型); 這是為了友善開發人員定位錯誤消息等。

關于MessageCodesResolver和預設政策的更多資訊可以分别在javadoc MessageCodesResolver 和 of中找到 DefaultMessageCodesResolver。

四、Bean操作和BeanWrapper

該org.springframework.beans軟體包符合Oracle提供的JavaBeans标準。JavaBean隻是一個帶有預設無參構造函數的類,它遵循命名約定,其中(通過示例)名為property的屬性bingoMadness将具有setter方法setBingoMadness(..)和getter方法getBingoMadness()。有關JavaBeans和規範的更多資訊,請參閱Oracle網站( javabeans)。

bean包中一個非常重要的類是BeanWrapper接口及其相應的實作(BeanWrapperImpl)。正如引用javadocs, BeanWrapper提供功能設定和擷取屬性值(單獨或批量),擷取屬性描述符,并查詢屬性,以确定它們是否可讀或可寫。此外,該産品BeanWrapper還支援嵌套屬性,可以将子屬性的屬性設定為無限深度。然後, BeanWrapper支援标準JavaBean的能力PropertyChangeListeners 和VetoableChangeListeners,而不需要在輔助代碼。最後但并非最不重要的是,BeanWrapper為設定索引屬性提供了支援。在BeanWrapper通常不使用應用程式代碼直接的,而是由DataBinder和BeanFactory。

BeanWrapper作品部分由其名稱表示的方式:它包裝一個bean以對該bean執行操作,例如設定和檢索屬性。

1.設定和擷取基本和嵌套的屬性

設定和擷取屬性是通過使用setPropertyValue(s)和 getPropertyValue(s)兩個重載變量都有的方法完成的。它們都在Spring的javadocs中有更詳細的描述。重要的是要知道有幾個約定用于訓示對象的屬性。幾個例子:

Examples of properties

Expression Explanation
name 訓示與name方法getName()或isName() 和相對應的屬性setName(..)
account.name 訓示對應于例如方法或屬性name的屬性的嵌套屬性accountgetAccount().setName()getAccount().getName()
account[2] 訓示索引屬性的第三個元素account。索引屬性可以是類型的array,也可以是list其他自然順序的集合
account[COMPANYNAME] 訓示由Map屬性的鍵COMPANYNAME索引的地圖條目的值account

如果你不打算BeanWrapper直接使用它DataBinder,那麼下一部分對你來說并不是非常重要,如果你隻是使用BeanFactory 和他們的開箱即用的實作,你應該跳到關于 PropertyEditors。)

考慮以下兩個類:

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;
    }
}           

下面的代碼片斷展示了如何檢索和操作的一些執行個體化屬性的一些例子Companies和Employees:

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");           

2.内置的PropertyEditor實作

Spring使用概念PropertyEditors來實作an Object和a 之間的轉換 String。如果你仔細想想,有時候可能會很友善地以不同于對象本身的方式表示屬性。例如,Date 可以用人類可讀的方式表示(如String ‘2007-14-09’),而我們仍然能夠将人類可讀形式轉換回原始日期(或者甚至更好:将任何以人類可讀形式輸入的日期轉換回到Date物體)。這種行為可以通過注冊類型的 自定義編輯器來實作java.beans.PropertyEditor。BeanWrapper在上一章中提到的在特定的IoC容器中注冊自定義編輯器,或者将自定義編輯器注冊到特定的IoC容器中,使其知道如何将屬性轉換為所需的類型。閱讀更多 PropertyEditors在java.beansOracle提供的軟體包的javadoc中。

Spring中使用屬性編輯的幾個示例:

  • 在bean上設定屬性是使用完成的PropertyEditors。當提到 java.lang.String某個bean的屬性的值時,你将在XML檔案中聲明,Spring将(如果相應屬性的setter具有 Class-parameter)使用該ClassEditor方法嘗試将參數解析為Class 對象。
  • 在Spring的MVC架構中解析HTTP請求參數是使用各種PropertyEditors你可以手動綁定的所有子類來完成的 CommandController。

Spring有一些内置的功能,PropertyEditors讓生活變得輕松。這些都列在下面,它們都位于org.springframework.beans.propertyeditors 包中。大多數(但不是全部)(如下所示)預設通過 BeanWrapperImpl。在以某種方式配置屬性編輯器的情況下,您當然可以注冊自己的變體以覆寫預設的變體:

表12.内置的PropertyEditors

Class
ByteArrayPropertyEditor 位元組數組編輯器。字元串将被簡單地轉換為相應的位元組表示。通過預設注冊BeanWrapperImpl。
ClassEditor 将表示類的字元串解析為實際的類以及其他方式。當沒有找到類時,IllegalArgumentException會抛出一個類。通過預設注冊 BeanWrapperImpl。
CustomBooleanEditor 可定制的屬性編輯器Boolean。通過預設注冊 BeanWrapperImpl,但可以通過将自定義執行個體注冊為自定義編輯器來覆寫。
CustomCollectionEditor Collections的屬性編輯器,将任何源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對象。通過預設注冊BeanWrapperImpl。
StringTrimmerEditor 修剪字元串的屬性編輯器。可以選擇允許将空字元串轉換為null值。沒有預設注冊; 必須根據需要進行使用者注冊。
URLEditor 能夠将URL的字元串表示形式解析為實際URL對象。通過預設注冊BeanWrapperImpl。

Spring使用它java.beans.PropertyEditorManager來設定可能需要的屬性編輯器的搜尋路徑。搜尋路徑還包括sun.bean.editors,其包括PropertyEditor實作為類型,例如Font,Color和最原始類型。還要注意的是,标準的JavaBean基礎構架能夠自動發現PropertyEditor類(無需做額外的注冊工作),如果他們是在同一個包,因為他們處理類,并具有相同的名稱作為類,它有’Editor’附加; 例如,可以有以下的類和包結構,這足以使FooEditor該類被識别并用作PropertyEditorfor- Footype屬性。=

com
  chank
    pop
      Foo
      FooEditor // the PropertyEditor for the Foo class           

請注意,您也可以在BeanInfo此處使用标準的JavaBeans機制(在此處 以非驚人的細節描述)。在下面查找使用該BeanInfo機制來顯式注冊一個或多個PropertyEditor執行個體以及關聯類的屬性的示例。

com
  chank
    pop
      Foo
      FooBeanInfo // the BeanInfo for the Foo class           

以下是引用FooBeanInfo類的Java源代碼。這将把a CustomNumberEditor與班級的age财産聯系起來Foo。

public class FooBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}           

3.注冊更多的定制PropertyEditor

将bean屬性設定為字元串值時,Spring IoC容器最終将使用标準JavaBeans PropertyEditors将這些字元串轉換為該屬性的複雜類型。Spring預注冊了一些自定義PropertyEditors(例如,将表示為字元串的類名轉換為真實Class對象)。另外,Java标準的JavaBeans PropertyEditor查找機制允許一個PropertyEditor 類隻需簡單地命名,并放入與其提供支援的類相同的包中,以便自動找到。

如果需要注冊其他自定義PropertyEditors,則有幾種可用的機制。大多數手動方法,通常不友善或不推薦,隻是簡單地使用接口的registerCustomEditor()方法 ConfigurableBeanFactory,假設您有BeanFactory參考。另一種稍微更友善的機制是使用一個叫做特殊的bean工廠後處理器CustomEditorConfigurer。雖然bean factory後處理器可以與BeanFactory實作一起使用,但CustomEditorConfigurer它具有嵌套屬性設定,是以強烈建議将它與該 ApplicationContextbean配合使用,并以與其他Bean類似的方式部署,并自動檢測并應用。

請注意,所有的bean工廠和應用程式上下文自動使用一些内置的屬性編輯器,通過使用稱為a的東西BeanWrapper來處理屬性轉換。标準屬性編輯器,BeanWrapper 寄存器在上一節中列出。此外, ApplicationContexts還可以覆寫或添加額外數量的編輯器,以适合特定應用程式上下文類型的方式處理資源查找。

标準JavaBeans PropertyEditor執行個體用于将以字元串表示的屬性值轉換為屬性的實際複雜類型。 CustomEditorConfigurer,一個bean工廠後處理器,可以被用來友善地為其他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;
    }
}           

當事情設定正确時,我們希望能夠将類型屬性指定為字元串,後者PropertyEditor将在背景轉換為實際的 ExoticType執行個體:

<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注冊新PropertyEditor的 ApplicationContext,然後可以根據需要使用它:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>           

4.使用PropertyEditorRegistrars

向Spring容器注冊屬性編輯器的另一種機制是建立和使用a PropertyEditorRegistrar。當您需要在幾種不同情況下使用同一組屬性編輯器時,此接口特别有用:編寫相應的注冊器并在每種情況下重新使用它。PropertyEditorRegistrars與稱為PropertyEditorRegistry的接口一起工作,接口由Spring BeanWrapper(和DataBinder)實作。PropertyEditorRegistrars 在與這裡CustomEditorConfigurer 介紹的(在這裡介紹)結合使用時會特别友善,它暴露了一個名為setPropertyEditorRegistrars(..):PropertyEditorRegistrars以CustomEditorConfigurer這種方式添加到一個 屬性,可以很容易地與DataBinderSpring MVC 共享Controllers。此外,它避免了在定制編輯器上同步的需要:aPropertyEditorRegistrar預計會PropertyEditor 為每個bean建立嘗試建立新的執行個體。

使用一個PropertyEditorRegistrar例子可能是最好的例子。首先,你需要建立你自己的PropertyEditorRegistrar實作:

package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // it is expected that new PropertyEditor instances are created
        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)結合使用可能非常友善。下面查找一個PropertyEditorRegistrar在實作initBinder(..)方法中使用a的示例:

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 注冊碼封裝在一個類中,然後在Controllers需要的時候共享 。

五、Spring Type Conversion

Spring 3引入了一個core.convert提供通用類型轉換系統的包。系統定義了一個SPI來實作類型轉換邏輯,以及一個在運作時執行類型轉換的API。在Spring容器中,該系統可以用作PropertyEditors的替代方法,将外部化的bean屬性值字元串轉換為所需的屬性類型。公共API也可用于需要進行類型轉換的應用程式中的任何位置。

1.轉換器SPI(用于類型間的轉換操作)

實作類型轉換邏輯的SPI非常簡單且強類型化:

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);

}           

要建立您自己的轉換器,隻需實作上面的界面。參數S 化為您要轉換的類型,以及轉換T為的類型。這樣的轉換器也可以透明地應用,如果S需要将一個集合或一組需要轉換為一個數組或集合T,前提是委托數組/收集轉換器也已被注冊(DefaultConversionService預設情況下)。

對于每次調用convert(S),源參數保證不為空。如果轉換失敗,您的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);
    }

}           

2.ConverterFactory

當您需要為整個類層次結構集中轉換邏輯時(例如,從String轉換為java.lang.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.GenericConverter

當您需要複雜的Converter實作時,請考慮GenericConverter接口。使用更靈活但類型較弱的簽名,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提供對設定轉換值的目标字段的通路權限。

GenericConverter的一個很好的例子是在Java Array和Collection之間轉換的轉換器。這樣的ArrayToCollectionConverter内省了聲明目标Collection類型的字段來解析Collection的元素類型。這允許源數組中的每個元素在目标字段上設定Collection之前轉換為Collection元素類型。

由于GenericConverter是一個更複雜的SPI接口,是以隻有在需要時才使用它。Favor Converter或ConverterFactory用于基本類型轉換需求。

4.ConditionalGenericConverter

有時你隻想要一個Converter執行,如果一個特定的條件成立。例如,Converter如果目标字段上存在特定的注釋,則可能隻想執行一次。或者,Converter如果static valueOf在目标類上定義了特定方法(如方法),則可能隻想執行一次。

ConditionalGenericConverter

是允許您定義這種自定義比對條件的接口

GenericConverter

ConditionalConverter

接口的結合:

public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}

public interface ConditionalGenericConverter
    extends GenericConverter, ConditionalConverter {

}           

一個很好的例子ConditionalGenericConverter是一個EntityConverter,它在一個持久化實體辨別符和一個實體引用之間進行轉換。如果目标實體類型聲明靜态查找方法,例如,這樣的EntityConverter可能隻比對 findAccount(Long)。你會在執行中執行這種查找方法檢查 matches(TypeDescriptor, TypeDescriptor)。

5.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

配置提供了便利的工廠。

6.Configuring a ConversionService

ConversionService是一個無狀态對象,設計用于在應用程式啟動時執行個體化,然後在多個線程之間共享。在Spring應用程式中,您通常為每個Spring容器(或ApplicationContext)配置一個ConversionService執行個體。該轉換服務将被Spring接收,然後在架構需要執行類型轉換時使用。您也可以将此ConversionService注入到任何bean中并直接調用它。

如果沒有使用Spring注冊ConversionService,則使用原始的基于PropertyEditor的系統。

要使用Spring注冊預設的ConversionService,請添加以下帶有id的bean定義conversionService:

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>           

預設的ConversionService可以在字元串,數字,枚舉,Map,地圖和其他常用類型之間進行轉換。要用自己的自定義轉換器補充或覆寫預設轉換器,請設定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章節中的轉換和格式化。

在某些情況下,您可能希望在轉換過程中應用格式。有關使用的詳細資訊, 請參閱 FormatterRegistry SPIFormattingConversionServiceFactoryBean。

未完待續~~~