BeanWrapper的工作方式在一定程度上是由它的名字表示:它包装一个Bean在其上执行动作,例如设置和检索属性。
在beans包中的一个很重要的类是BeanWrapper接口和它响应的实现(BeanWrapperImpl)。BeanWrapper提供功能(单独或批量)设置和获取属性值,获取属性描述,和查询属性决定它们是否可读或可写。同时,BeanWrapper提供支持嵌套属性,能够设置属性的无限深度的子属性。此外,BeanWrapper支持添加标准JavaBeans的PropertyChangeListeners和VetoableChangeListener,无需在目标类中提供代码。最后但并不是最不重要的,BeanWrapper提供设置或索引属性支持。BeanWrapper通常并不直接用于应用代码,但用于DataBinder和BeanFactory。
BeanWrapper的工作方式在一定程度上是由它的名字表示:它包装一个Bean在其上执行动作,例如设置和检索属性。
1 设置和获取基础和嵌套属性
使用setPropertyValue(s)和getPropertyValue(s)方法设置和获取属性,都有两个变体。更详细的描述见Spring的Javadoc中。
表 属性的例子
表达式 | 描述 |
name | 表示属性名name对应方法getName()或isName()和setName()。 |
account.name | 表示属性account的嵌套属性name,对应方法getAccount().setName()或getAccount().getName()。 |
account[2] | 表示索引属性account的第三个元素。索引属性的类型可以是数组、list或其他自然排序集合。 |
account[COMPANYNAME] | 表示Map实体索引的值,键为COMPENYNAME。 |
下面你会发现的一些示例使用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;
}
}
下面代码片段显示一些例子,如何检索和操作Companies和Employees实例的一些属性:
BeanWrapper company = new BeanWrapperImpl(new Company());
// 设置公司名称
company.setPropertyValue("name", "Some Company Inc.");
// 也可以这样
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);// 创建一个主管并绑定到公司
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());// 通过公司检索主管的薪水
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
2 内置PropertyEditor实现
Spring使用PropertyEditor的概念作用于Object和String之间的转换。如果你了解它,有时可能会方便以不同的方式表示属性而不是对象本身。例如,Date可以表示为一个人类可读的方式(作为字符串’2007-14-09’),然而,我们任然能够将人类可读回溯到原始日期(甚至更好:转换任意日期为人类可读形式,回溯至Date对象)。这种行为可以通过注册java.beans.PropertyEditor类型的自定义编辑器。在BeanWrapper上注册自定义编辑器或在一个如前一节中所述特定的IoC容器中,指定如何将属性转换为特定类型。
在Spring中编辑属性的一些例子:
- 在Bean上使用PropertyEditors设置属性。当java.lang.String作为某些Bean的属性值声明在XML文件中,(如果设置相应属性有Class参数)Spring将使用ClassEditor尝试解析参数为一个Class对象。
- 在Spring的MVC框架中使用所有类型的PropertyEditors解析HTTP请求参数,你能手动绑定CommandController的所有子类。
Spring有一系列的内置PropertyEditors。每个PropertyEditors都列在org.springframework.beans.propertyeditors包中。大多数,但不是所有(如下所示),默认通过BeanWrapperImpl注册。以某种方式属性编辑器是可配置的,你当然还可以注册自己的变体来覆盖默认的属性编辑器:
表 内置属性编辑器
类 |
说明 |
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对象,反之亦然(字符串格式是[国家][变体],Locale的toString()方法提供了相同的功能)。默认由BeanWrapperImpl注册。 |
PatternEditor | 能够解析字符串为java.util.regex.Pattern对象,反之亦然。 |
PropertiesEditor | 能够(使用定义在java.util.Properties类的javadocs的格式)转换字符串为Properties对象。默认由BeanWrapperImpl注册。 |
StringTrimmerEditor | 去掉字符串两边空格的属性编辑器。允许选择将一个空字符串转换为null。默认没有注册,必须用户注册。 |
URLEditor | 能够解析代表URL的字符串为一个真实URL对象。默认由BeanWrapperImpl注册。 |
Spring使用java.beans.PropertyEditorManager设置搜索需要的属性编辑器的路径。搜索路径也包括sun.bean.editors,包括PropertyEditor实现类型,例如Font、Color和大多数原始类型。也要注意,标准JavaBeans基础设施将自动发现PropertyEditor类(你不必显示注册它们)如果他它们和处理的类在同一个包中,并与处理的类相同的名称,使用Editor结尾;例如,下面有一个可能的类和包结构,FooEditor将被认为是使用Foo类型属性的PropertyEditor 。
com
chank
pop
Foo
FooEditor // Foo类的PropertyEditor
注意,你也能在这里使用标准BeanInfo JavaBeans机制。找到下面的一个例子使用BeanInfo机制明确注册一个或多个PropertyEditor实例的属性相关联的类。
com
chank
pop
Foo
FooBeanInfo // Foo类的BeanInfo
以下是引用FooBeanInfo类的Java源代码。这将关联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());
}
}
}
注册额外的自定义PropertyEditors
当设置Bean属性作为字符串值,Spring IoC容器最后使用标准JavaBeans PropertyEditor转换这些字符串为复杂属性类型。Spring预先注册了一系列自定义PropertyEditor(例如,为了转换类名字符串为真实Class对象)。此外,Java的标准JavaBeans PropertyEditor查找机制允许PropertyEditor的类仅仅是适当的命名并放置在同一个包中的类提供了自动查找的支持。
如果需要注册其他自定义PropertyEditor,有几种机制。大多数手动方式,通常不方便或不建议使用,简单的使用ConfigurableBeanFactory接口的registerCustomEditor()方法,假设你有BeanFactory引用。另一种,更方便的机制是使用称为CustomEditorConfigurer的特定Bean工厂后处理器。尽管Bean工厂后处理器能与BeanFactory实现一起使用,CustomEditorConfigurer有一个嵌套属性设置,因此强烈建议使用ApplicationContext,它可以部署在类似于任何其它Bean的地方,并自动检测和应用。
注意,所有Bean工厂和应用上下文自动使用一系列内置属性编辑器,通过使用一种叫做BeanWrapper的东西来处理属性转换。BeanWrapper注册的标准属性编辑器列在文章之前的小节。除此之外,ApplicationContext也覆盖或添加一个额外的编辑器处理适合于特定的应用程序上下文类型的资源查找方式。
标准JavaBeans PropertyEditor实例用于转换表示为字符串的属性值为真实的复杂类型属性。CustomEditorConfigurer,一个Bean工厂后处理器,可以用于方便的为ApplicationContext添加额外的PropertyEditor实例支持。
思考一个用户类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实现可能类似于:
// 转换字符串为ExoticType对象
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>
使用PropertyEditorRegistrars
另一种使用Spring容器注册属性编辑器的机制是创建并使用一个PropertyEditorRegistrars。该接口是特别有用的,当你需要在不同情况下时使用相同属性编辑器集:编写一个相应的注册器并在每种情况下重用。
PropertyEditorRegistrars和一个叫PropertyEditorRegistry的接口一起使用,一个实现Spring BeanWrapper(和DataBinder)的接口。PropertyEditorRegistrars特别方便,当联合CustomEditorConfigurer使用时,调用setPropertyEditorRegistrars(. .)暴露属性:PropertyEditorRegistrars以这种方式添加一个CustomEditorConfigurer可以很容易地在DataBinder和Spring MVC控制器之间共享。此外,它避免了需要同步自定义编辑器:PropertyEditorRegistrar预计将为每个bean创建新的PropertyEditor实例创建。
使用PropertyEditorRegistrar也许最好的用一个例子来阐述。首先,你需要创建自己的PropertyEditorRegistrar实现:
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 期望创建新的PropertyEditor实例
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());// 你可能在这里定义许多需要的自定义属性编辑器
}
}
实现PropertyEditorRegistrar的例子见org.springframework.beans.support.REsourceEditorRegistrar。注意,在实现的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框架,联合使用PropertyEditorRegistrar和数据绑定控制器(例如SimpleFormController)非常方便。
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);
}// 注册用户的其它方法
}
这种风格的PropertyEditor注册可以导致简洁的代码(实现initBinder(..)是一条长线),并允许共同PropertyEditor登记代码封装在一个类,然后尽可能多的控制器之间需要共享。