?
本文檔使用 php中文網(wǎng)手冊 發(fā)布
org.springframework.beans
包遵循Sun發(fā)布的JavaBean標(biāo)準(zhǔn)。JavaBean是一個簡單的含有一個默認(rèn)無參數(shù)構(gòu)造函數(shù)的Java類, 這個類中的屬性遵循一定的命名規(guī)范,且具有setter和getter方法。例如,某個類擁有一個叫做bingoMadness
的屬性,并同時具有與該屬性對應(yīng)的setter方法:setBingoMadness(..)
和getter方法:getBingoMadness()
。如果你需要了解JavaBean規(guī)范的詳細(xì)信息可以訪問Sun的網(wǎng)站 (java.sun.com/products/javabeans)。
這個包中的一個非常重要的類就是BeanWrapper
接口以及它對應(yīng)的實現(xiàn)(BeanWrapperImpl
)。根據(jù)JavaDoc中的說明,BeanWrapper
提供了設(shè)置和獲取屬性值(單個的或者是批量的),獲取屬性描述信息、查詢只讀或者可寫屬性等功能。不僅如此,BeanWrapper
還支持嵌套屬性,你可以不受嵌套深度限制對子屬性的值進(jìn)行設(shè)置。所以,BeanWrapper
無需任何輔助代碼就可以支持標(biāo)準(zhǔn)JavaBean的PropertyChangeListeners
和VetoableChangeListeners
。除此之外,BeanWrapper
還提供了設(shè)置索引屬性的支持。通常情況下,我們不在應(yīng)用程序中直接使用BeanWrapper
而是使用DataBinder
和BeanFactory
。
BeanWrapper
這個名字本身就暗示了它的功能:封裝了一個bean的行為,諸如設(shè)置和獲取屬性值等。
設(shè)置和獲取屬性可以通過使用重載的setPropertyValue(s)
和getPropertyValue(s)
方法來完成。在Spring自帶的JavaDoc中對它們有詳細(xì)的描述。值得一提的是,在這其中存在一些針對對象屬性的潛在約定規(guī)則。下面是一些例子:
表?5.1.?屬性示例
表達(dá)式 | 說明 |
---|---|
name |
表明屬性name與方法getName() 或 isName() 及 setName(..) 相對應(yīng)。 |
account.name |
指向?qū)傩詀ccount的嵌套屬性name,與之對應(yīng)的是getAccount().setName()和getAccount().getName() |
account[2] |
指向索引屬性account的第三個元素,索引屬性可能是一個數(shù)組(array),列表(list)或其它天然有序的容器。 |
account[COMPANYNAME] |
指向一個Map實體account中以COMPANYNAME作為鍵值(key)所對應(yīng)的值 |
在下面的例子中你將看到一些使用BeanWrapper
設(shè)置屬性的例子。
如果你不打算直接使用BeanWrapper
,這部分不是很重要。如果你僅僅使用DataBinder
和BeanFactory
或者他們的擴(kuò)展實現(xiàn),你可以跳過這部分直接閱讀PropertyEditor
的部分。
考慮下面兩個類:
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; } }
下面的代碼片斷展示了如何獲取和設(shè)置上面兩個示例類 Companies
和Employees
的屬性:
BeanWrapper company = 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 = 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");
Spring大量使用了PropertyEditor
以在Object
和 String
之間進(jìn)行有效地轉(zhuǎn)化。仔細(xì)想想,有時換一種方式來展示屬性的確要比直接用對象自身更容易讓人理解。例如,Date
可以表示成人們易讀的方式(如String
的方式,'2007-14-09
'),與此同時我們可以將這種人們比較容易理解的形式轉(zhuǎn)化為原有的原始Date類型(甚至對于任何人們輸入的可理解的日期形式都可以轉(zhuǎn)化成相應(yīng)的Date
對象)。要做到這點,可以通過注冊一個用戶定制編輯器(類型為java.beans.PropertyEditor
)來完成。注冊一個用戶自定義的編輯器可以告訴BeanWrapper
我們將要把屬性轉(zhuǎn)換為哪種類型。正如在先前章節(jié)提到的,另外一種選擇是在特定的IoC 容器中完成注冊。你可以從Sun提供的java.beans
包的JavaDoc中了解到更多PropertyEditors
的細(xì)節(jié)。
屬性編輯器主要應(yīng)用在以下兩個方面:
使用PropertyEditors
設(shè)置Bean屬性。當(dāng)你在XML文件中聲明的bean的屬性類型為java.lang.String時,Spring將使用ClassEditor
將String解析成Class對象(如果setter方法需要一個Class
參數(shù)的話)。
在Spring MVC架構(gòu)中使用各種PropertyEditors
來解析HTTP請求中的參數(shù)。你可以用各種CommandController
的子類來進(jìn)行手工綁定。
Spring提供了許多內(nèi)建的PropertyEditors
可以簡化我們的工作。下面的列表列出了所有Spring自帶的PropertyEditor
,它們都位于org.springframework.beans.
包內(nèi)。它們中的大多數(shù)已經(jīng)默認(rèn)在PropertyEditor
sBeanWrapperImpl
的實現(xiàn)類中注冊好了。作為可配置的選項,你也可以注冊你自己的屬性編輯器實現(xiàn)去覆蓋那些默認(rèn)編輯器。
表?5.2.?內(nèi)建的PropertyEditors
類名 | 說明 |
---|---|
ByteArrayPropertyEditor |
byte數(shù)組編輯器。字符串將被簡單轉(zhuǎn)化成他們相應(yīng)的byte形式。在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了。 |
ClassEditor |
將以字符串形式出現(xiàn)的類名解析成為真實的Class對象或者其他相關(guān)形式。當(dāng)這個Class沒有被找到,會拋出一個IllegalArgumentException 的異常,在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了。
|
CustomBooleanEditor |
為Boolean 類型屬性定制的屬性編輯器。在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了,但可以被用戶自定義的編輯器實例覆蓋其行為。
|
CustomCollectionEditor |
集合(Collection )編輯器,將任何源集合(Collection )轉(zhuǎn)化成目標(biāo)的集合類型的對象。
|
CustomDateEditor |
為java.util.Date類型定制的屬性編輯器,支持用戶自定義的DateFormat。默認(rèn)沒有被BeanWrapper Impl注冊,需要用戶通過指定恰當(dāng)?shù)膄ormat類型來注冊。
|
CustomNumberEditor |
為Integer , Long , Float , Double 等Number的子類定制的屬性編輯器。在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了,但可以被用戶自己定義的編輯器實例覆蓋其行為。
|
FileEditor |
能夠?qū)⒆址D(zhuǎn)化成java.io.File 對象,在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了。
|
InputStreamEditor |
一個單向的屬性編輯器,能夠把文本字符串轉(zhuǎn)化成InputStream (通過ResourceEditor 和 Resource 作為中介),因而InputStream 屬性可以直接被設(shè)置成字符串。注意在默認(rèn)情況下,這個屬性編輯器不會為你關(guān)閉InputStream 。在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了。
|
LocaleEditor |
在String對象和Locale 對象之間互相轉(zhuǎn)化。(String的形式為[語言]_[國家]_[變量],這與Local對象的toString()方法得到的結(jié)果相同)在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了。
|
PatternEditor |
可以將字符串轉(zhuǎn)化為JDK 1.5的
Pattern 對象,反之亦然。 |
PropertiesEditor |
能將String轉(zhuǎn)化為Properties 對象(由JavaDoc規(guī)定的java.lang.Properties類型的格式)。在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了。
|
StringTrimmerEditor |
一個用于修剪(trim)String類型的屬性編輯器,具有將一個空字符串轉(zhuǎn)化為null 值的選項。默認(rèn)沒有注冊,必須由用戶在需要的時候自行注冊。
|
URLEditor |
能將String表示的URL轉(zhuǎn)化為一個具體的URL 對象。在BeanWrapperImpl 中已經(jīng)默認(rèn)注冊好了。
|
Spring使用java.beans.PropertyEditorManager
來為可能需要的屬性編輯器設(shè)置查詢路徑。查詢路徑同時包含了sun.bean.editors
,這個包中定義了很多PropertyEditor
的具體實現(xiàn),包括Font
、Color
以及絕大多數(shù)的基本類型的具體實現(xiàn)。同樣值得注意的是,標(biāo)準(zhǔn)的JavaBean基礎(chǔ)構(gòu)架能夠自動識別PropertyEditor
類(無需做額外的注冊工作),前提條件是,類和處理這個類的Editor位于同一級包結(jié)構(gòu),而Editor的命名遵循了在類名后加了“Editor”的規(guī)則。舉例來說,當(dāng)FooEditor
和Foo
在同一級別包下的時候,FooEditor
能夠識別Foo
類并作為它的PropertyEditor
。
com
chank
pop
Foo
FooEditor // the PropertyEditor
for the Foo
class
注意,你同樣可以使用標(biāo)準(zhǔn)的BeanInfo
JavaBean機(jī)制(詳情見這里)。在下面的例子中,你可以看到一個通過使用BeanInfo
機(jī)制來為相關(guān)類的屬性明確定義一個或者多個PropertyEditor
實例。
com
chank
pop
Foo
FooBeanInfo // the BeanInfo
for the Foo
class
下面就是FooBeanInfo
類的源碼,它將CustomNumberEditor
與Foo
中的age
屬性聯(lián)系在了一起。
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()); } } }
當(dāng)以一個字符串值來設(shè)置bean屬性時,Spring IoC 容器最終使用標(biāo)準(zhǔn)的JavaBean PropertyEditor
來將這些字符串轉(zhuǎn)化成復(fù)雜的數(shù)據(jù)類型。Spring預(yù)先注冊了一些PropertyEditor
(舉例來說,將一個以字符串表示的Class轉(zhuǎn)化成Class
對象)。除此之外,Java標(biāo)準(zhǔn)的JavaBean PropertyEditor
會識別在同一包結(jié)構(gòu)下的類和它對應(yīng)的命名恰當(dāng)?shù)腅ditor,并自動將其作為這個類的的Editor。
如果你想注冊自己定義的PropertyEditor
,那么有幾種不同的機(jī)制供君選擇。其中,最原始的手工方式是在你有一個BeanFactory
的引用實例時,使用ConfigurableBeanFactory
的registerCustomEditor()
方法。當(dāng)然,通常這種方法不夠方便,因而并不推薦使用。另外一個簡便的方法是使用一個稱之為CustomEditorConfigurer
的特殊的bean factory后置處理器。盡管bean factory的后置處理器可以半手工化的與BeanFactory
實現(xiàn)一起使用,但是它存在著一個嵌套屬性的建立方式。因此,強烈推薦的一種做法是與ApplicationContext
一起來使用它。這樣就能使之與其他的bean一樣以類似的方式部署同時被容器所感知并使用。
注意所有的bean factory和application context都會自動地使用一系列的內(nèi)置屬性編輯器,通過BeanWrapper
來處理屬性的轉(zhuǎn)化。在這里列出一些在BeanWrapper
中注冊的標(biāo)準(zhǔn)的屬性編輯器。除此之外,ApplicationContext
覆蓋了一些默認(rèn)行為,并為之增加了許多編輯器來處理在某種意義上合適于特定的application context類型的資源查找。
標(biāo)準(zhǔn)的JavaBean的PropertyEditor
實例將以String表示的值轉(zhuǎn)化成實際復(fù)雜的數(shù)據(jù)類型。CustomEditorConfigurer
作為一個bean factory的后置處理器,能夠便捷地將一些額外的PropertyEditor
實例加入到ApplicationContext
中去。
考慮用戶定義的類ExoticType
和DependsOnExoticType
,其中,后者需要將前者設(shè)置為它的屬性:
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; } }
在一切建立起來以后,我們希望通過指定一個字符串來設(shè)置type屬性的值,然后PropertyEditor
將在幕后幫你將其轉(zhuǎn)化為實際的ExoticType
對象:
<bean id="sample" class="example.DependsOnExoticType"> <property name="type" value="aNameForExoticType"/> </bean>
PropertyEditor
的實現(xiàn)看上去就像這樣:
// converts string representation to ExoticType
object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
private String format;
public void setFormat(String format) {
this.format = format;
}
public void setAsText(String text) {
if (format != null && format.equals("upperCase")) {
text = text.toUpperCase();
}
ExoticType type = new ExoticType(text);
setValue(type);
}
}
最后,我們通過使用CustomEditorConfigurer
來為ApplicationContext
注冊一個新的PropertyEditor
,這樣,我們就可以在任何需要的地方使用它了:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="example.ExoticType"> <bean class="example.ExoticTypeEditor"> <property name="format" value="upperCase"/> </bean> </entry> </map> </property> </bean>
將屬性編輯器注冊到Spring容器的另一種方式是創(chuàng)建并使用PropertyEditorRegistrar
。
當(dāng)你在不同情況下(如編寫一個相應(yīng)的注冊器然后在多種情況下重用它)需要使用相同的屬性編輯器時該接口特別有用。
PropertyEditorRegistrars
與PropertyEditorRegistry
接口協(xié)同工作,
PropertyEditorRegistry
是一個由Spring的BeanWrapper
(及DataBinder
)實現(xiàn)的一個接口。
當(dāng)與CustomEditorConfigurer
(在此處曾介紹過)協(xié)同使用時,
PropertyEditorRegistrars
尤為方便,
CustomEditorConfigurer
會暴露一個叫做setPropertyEditorRegistrars(..)
的方法:
在這種情況下DataBinder
和Spring MVC Controllers
會很容易地共享加到CustomEditorConfigurer
中的PropertyEditorRegistrars
。
此外,自定義編輯器無需再進(jìn)行同步了:每次創(chuàng)建bean時PropertyEditorRegistrar
都會創(chuàng)建一個新的PropertyEditor
。
通過示例最能說明PropertyEditorRegistrar
的優(yōu)點了。首先,你需要創(chuàng)建你自己的PropertyEditorRegistrar
實現(xiàn):
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
實現(xiàn)的范例。請注意在registerCustomEditors(..)
方法的實現(xiàn)中,它是如何創(chuàng)建每個屬性編輯器的實例的。
接下來我們配置一個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框架的應(yīng)用來說,與數(shù)據(jù)綁定Controllers
(例如SimpleFormController
)一同使用PropertyEditorRegistrars
會非常便捷。在下面的例子中,initBinder(..)
方法的實現(xiàn)使用了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(..)
的實現(xiàn)就一行代碼而已?。?,同時它使得通用的PropertyEditor
注冊代碼可被封裝到一個類中,然后在需要時被多個Controllers
共享。