org.springframework.beans
包遵循Sun发布的JavaBean标准。JavaBean是一个简单的含有一个默认无参数构造函数的Java类, 这个类中的属性遵循一定的命名规范,且具有setter和getter方法。例如,某个类拥有一个叫做bingoMadness
的属性,并同时具有与该属性对应的setter方法:setBingoMadness(..)
和getter方法:getBingoMadness()
。如果你需要了解JavaBean规范的详细信息可以访问Sun的网站 (java.sun.com/products/javabeans)。
这个包中的一个非常重要的类就是BeanWrapper
接口以及它对应的实现(BeanWrapperImpl
)。根据JavaDoc中的说明,BeanWrapper
提供了设置和获取属性值(单个的或者是批量的),获取属性描述信息、查询只读或者可写属性等功能。不仅如此,BeanWrapper
还支持嵌套属性,你可以不受嵌套深度限制对子属性的值进行设置。所以,BeanWrapper
无需任何辅助代码就可以支持标准JavaBean的PropertyChangeListeners
和VetoableChangeListeners
。除此之外,BeanWrapper
还提供了设置索引属性的支持。通常情况下,我们不在应用程序中直接使用BeanWrapper
而是使用DataBinder
和BeanFactory
。
BeanWrapper
这个名字本身就暗示了它的功能:封装了一个bean的行为,诸如设置和获取属性值等。
设置和获取属性可以通过使用重载的setPropertyValue(s)
和getPropertyValue(s)
方法来完成。在Spring自带的JavaDoc中对它们有详细的描述。值得一提的是,在这其中存在一些针对对象属性的潜在约定规则。下面是一些例子:
表 5.1. 属性示例
表达式 | 说明 |
---|---|
name |
表明属性name与方法getName() 或 isName() 及 setName(..) 相对应。 |
account.name |
指向属性account的嵌套属性name,与之对应的是getAccount().setName()和getAccount().getName() |
account[2] |
指向索引属性account的第三个元素,索引属性可能是一个数组(array),列表(list)或其它天然有序的容器。 |
account[COMPANYNAME] |
指向一个Map实体account中以COMPANYNAME作为键值(key)所对应的值 |
在下面的例子中你将看到一些使用BeanWrapper
设置属性的例子。
如果你不打算直接使用BeanWrapper
,这部分不是很重要。如果你仅仅使用DataBinder
和BeanFactory
或者他们的扩展实现,你可以跳过这部分直接阅读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; } }
下面的代码片断展示了如何获取和设置上面两个示例类 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
之间进行有效地转化。仔细想想,有时换一种方式来展示属性的确要比直接用对象自身更容易让人理解。例如,Date
可以表示成人们易读的方式(如String
的方式,'2007-14-09
'),与此同时我们可以将这种人们比较容易理解的形式转化为原有的原始Date类型(甚至对于任何人们输入的可理解的日期形式都可以转化成相应的Date
对象)。要做到这点,可以通过注册一个用户定制编辑器(类型为java.beans.PropertyEditor
)来完成。注册一个用户自定义的编辑器可以告诉BeanWrapper
我们将要把属性转换为哪种类型。正如在先前章节提到的,另外一种选择是在特定的IoC 容器中完成注册。你可以从Sun提供的java.beans
包的JavaDoc中了解到更多PropertyEditors
的细节。
属性编辑器主要应用在以下两个方面:
使用
PropertyEditors
设置Bean属性。当你在XML文件中声明的bean的属性类型为java.lang.String时,Spring将使用ClassEditor
将String解析成Class对象(如果setter方法需要一个Class
参数的话)。在Spring MVC架构中使用各种
PropertyEditors
来解析HTTP请求中的参数。你可以用各种CommandController
的子类来进行手工绑定。
Spring提供了许多内建的PropertyEditors
可以简化我们的工作。下面的列表列出了所有Spring自带的PropertyEditor
,它们都位于org.springframework.beans.
PropertyEditor
s包内。它们中的大多数已经默认在BeanWrapperImpl
的实现类中注册好了。作为可配置的选项,你也可以注册你自己的属性编辑器实现去覆盖那些默认编辑器。
表 5.2. 内建的PropertyEditors
类名 | 说明 |
---|---|
ByteArrayPropertyEditor |
byte数组编辑器。字符串将被简单转化成他们相应的byte形式。在BeanWrapperImpl 中已经默认注册好了。 |
ClassEditor |
将以字符串形式出现的类名解析成为真实的Class对象或者其他相关形式。当这个Class没有被找到,会抛出一个IllegalArgumentException 的异常,在BeanWrapperImpl 中已经默认注册好了。
|
CustomBooleanEditor |
为Boolean 类型属性定制的属性编辑器。在BeanWrapperImpl 中已经默认注册好了,但可以被用户自定义的编辑器实例覆盖其行为。
|
CustomCollectionEditor |
集合(Collection )编辑器,将任何源集合(Collection )转化成目标的集合类型的对象。
|
CustomDateEditor |
为java.util.Date类型定制的属性编辑器,支持用户自定义的DateFormat。默认没有被BeanWrapper Impl注册,需要用户通过指定恰当的format类型来注册。
|
CustomNumberEditor |
为Integer , Long , Float , Double 等Number的子类定制的属性编辑器。在BeanWrapperImpl 中已经默认注册好了,但可以被用户自己定义的编辑器实例覆盖其行为。
|
FileEditor |
能够将字符串转化成java.io.File 对象,在BeanWrapperImpl 中已经默认注册好了。
|
InputStreamEditor |
一个单向的属性编辑器,能够把文本字符串转化成InputStream (通过ResourceEditor 和 Resource 作为中介),因而InputStream 属性可以直接被设置成字符串。注意在默认情况下,这个属性编辑器不会为你关闭InputStream 。在BeanWrapperImpl 中已经默认注册好了。
|
LocaleEditor |
在String对象和Locale 对象之间互相转化。(String的形式为[语言]_[国家]_[变量],这与Local对象的toString()方法得到的结果相同)在BeanWrapperImpl 中已经默认注册好了。
|
PatternEditor |
可以将字符串转化为JDK 1.5的
Pattern 对象,反之亦然。 |
PropertiesEditor |
能将String转化为Properties 对象(由JavaDoc规定的java.lang.Properties类型的格式)。在BeanWrapperImpl 中已经默认注册好了。
|
StringTrimmerEditor |
一个用于修剪(trim)String类型的属性编辑器,具有将一个空字符串转化为null 值的选项。默认没有注册,必须由用户在需要的时候自行注册。
|
URLEditor |
能将String表示的URL转化为一个具体的URL 对象。在BeanWrapperImpl 中已经默认注册好了。
|
Spring使用java.beans.PropertyEditorManager
来为可能需要的属性编辑器设置查询路径。查询路径同时包含了sun.bean.editors
,这个包中定义了很多PropertyEditor
的具体实现,包括Font
、Color
以及绝大多数的基本类型的具体实现。同样值得注意的是,标准的JavaBean基础构架能够自动识别PropertyEditor
类(无需做额外的注册工作),前提条件是,类和处理这个类的Editor位于同一级包结构,而Editor的命名遵循了在类名后加了“Editor”的规则。举例来说,当FooEditor
和Foo
在同一级别包下的时候,FooEditor
能够识别Foo
类并作为它的PropertyEditor
。
com
chank
pop
Foo
FooEditor // the PropertyEditor
for the Foo
class
注意,你同样可以使用标准的BeanInfo
JavaBean机制(详情见这里)。在下面的例子中,你可以看到一个通过使用BeanInfo
机制来为相关类的属性明确定义一个或者多个PropertyEditor
实例。
com
chank
pop
Foo
FooBeanInfo // the BeanInfo
for the Foo
class
下面就是FooBeanInfo
类的源码,它将CustomNumberEditor
与Foo
中的age
属性联系在了一起。
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()); } } }
当以一个字符串值来设置bean属性时,Spring IoC 容器最终使用标准的JavaBean PropertyEditor
来将这些字符串转化成复杂的数据类型。Spring预先注册了一些PropertyEditor
(举例来说,将一个以字符串表示的Class转化成Class
对象)。除此之外,Java标准的JavaBean PropertyEditor
会识别在同一包结构下的类和它对应的命名恰当的Editor,并自动将其作为这个类的的Editor。
如果你想注册自己定义的PropertyEditor
,那么有几种不同的机制供君选择。其中,最原始的手工方式是在你有一个BeanFactory
的引用实例时,使用ConfigurableBeanFactory
的registerCustomEditor()
方法。当然,通常这种方法不够方便,因而并不推荐使用。另外一个简便的方法是使用一个称之为CustomEditorConfigurer
的特殊的bean factory后置处理器。尽管bean factory的后置处理器可以半手工化的与BeanFactory
实现一起使用,但是它存在着一个嵌套属性的建立方式。因此,强烈推荐的一种做法是与ApplicationContext
一起来使用它。这样就能使之与其他的bean一样以类似的方式部署同时被容器所感知并使用。
注意所有的bean factory和application context都会自动地使用一系列的内置属性编辑器,通过BeanWrapper
来处理属性的转化。在这里列出一些在BeanWrapper
中注册的标准的属性编辑器。除此之外,ApplicationContext
覆盖了一些默认行为,并为之增加了许多编辑器来处理在某种意义上合适于特定的application context类型的资源查找。
标准的JavaBean的PropertyEditor
实例将以String表示的值转化成实际复杂的数据类型。CustomEditorConfigurer
作为一个bean factory的后置处理器,能够便捷地将一些额外的PropertyEditor
实例加入到ApplicationContext
中去。
考虑用户定义的类ExoticType
和DependsOnExoticType
,其中,后者需要将前者设置为它的属性:
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 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 {
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容器的另一种方式是创建并使用PropertyEditorRegistrar
。
当你在不同情况下(如编写一个相应的注册器然后在多种情况下重用它)需要使用相同的属性编辑器时该接口特别有用。
PropertyEditorRegistrars
与PropertyEditorRegistry
接口协同工作,
PropertyEditorRegistry
是一个由Spring的BeanWrapper
(及DataBinder
)实现的一个接口。
当与CustomEditorConfigurer
(在此处曾介绍过)协同使用时,
PropertyEditorRegistrars
尤为方便,
CustomEditorConfigurer
会暴露一个叫做setPropertyEditorRegistrars(..)
的方法:
在这种情况下DataBinder
和Spring MVC Controllers
会很容易地共享加到CustomEditorConfigurer
中的PropertyEditorRegistrars
。
此外,自定义编辑器无需再进行同步了:每次创建bean时PropertyEditorRegistrar
都会创建一个新的PropertyEditor
。
通过示例最能说明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框架的应用来说,与数据绑定Controllers
(例如SimpleFormController
)一同使用PropertyEditorRegistrars
会非常便捷。在下面的例子中,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
注册代码可被封装到一个类中,然后在需要时被多个Controllers
共享。