org.springframework.beans 包遵守 JavaBeans 标准。JavaBean 是一个具有默认无参数构造函数的类,它遵循一个命名惯例,(例如)一个名为 bingoMadness 的属性将有一个 setter 方法setBingoMadness(.)和一个 getter 方法 getBingoMadness()。关于 JavaBeans 和规范的更多信息,请参见 javabeans。
beans 包中一个相当重要的类是 BeanWrapper 接口及其相应的实现(BeanWrapperImpl)。正如在 javadoc 中引用的那样,BeanWrapper 提供了 set 和 get 属性值(单独或批量)、获取属性描述符以及查询属性以确定它们是否可读或可写的功能。此外,BeanWrapper 还提供了对嵌套属性的支持,使得对子属性的属性设置可以达到无限的深度。BeanWrapper 还支持添加标准的 JavaBeans PropertyChangeListeners 和 VetoableChangeListeners 的能力,而不需要在目标类中添加支持代码。最后但同样重要的是, BeanWrapper 提供了对设置索引属性的支持。BeanWrapper 通常不被应用程序代码直接使用,而是被 DataBinder 和 BeanFactory 使用。
BeanWrapper 的工作方式在一定程度上可以从它的名字中看出:它包装一个 Bean 来对该 Bean 进行操作,例如设置和检索属性。
set、get 、嵌套属性
set 和 get 属性是通过 BeanWrapper 的 setPropertyValue 和 getPropertyValue 重载方法变体完成的。详情请参见它们的 Javadoc。下表显示了这些约定的一些例子:
| 表达式 | 描述 |
|---|---|
| name | 表示与 getName()或 isName()和 setName(.)方法相对应的属性名称。 |
| account.name | 表示与(例如)getAccount().setName()或 getAccount().getName()方法相对应的属性 account 的嵌套属性名称。 |
| account[2] | 表示索引属性帐户的第三个元素。索引属性可以是数组、列表或其他自然有序的集合类型。 |
| account[COMPANYNAME] | 表示由账 account map 属性的 COMPANYNAME 键索引的 map 条目的值。 |
(如果你不打算直接使用 BeanWrapper,那么接下来的一节对你来说并不重要。如果你只使用 DataBinder 和 BeanFactory 以及它们的默认实现,你应该跳到关于 PropertyEditors 的部分)。
下面两个示例类使用 BeanWrapper 来获取和设置属性:
package cn.mrcode.study.springdocsread.data;/*** @author mrcode*/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;}}
package cn.mrcode.study.springdocsread.data;/*** @author mrcode*/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;}}
下面显示了如何操作他们属性的示例
package cn.mrcode.study.springdocsread.data;import org.springframework.beans.BeanWrapper;import org.springframework.beans.BeanWrapperImpl;import org.springframework.beans.PropertyValue;/*** @author mrcode*/public class DemoTest {public static void main(String[] args) {BeanWrapper company = new BeanWrapperImpl(new Company());// 设置 name 属性的值company.setPropertyValue("name", "Some Company Inc.");// ... 也可以这样设置 name 属性的值PropertyValue value = new PropertyValue("name", "Some Company Inc.");company.setPropertyValue(value);// 创建一个员工对象,并设置到 managingDirector 字段上BeanWrapper jim = new BeanWrapperImpl(new Employee());jim.setPropertyValue("name", "Jim Stravinsky");company.setPropertyValue("managingDirector", jim.getWrappedInstance());// 通过 company 实例,获取 managingDirector 属性上的 salary 字段Float salary = (Float) company.getPropertyValue("managingDirector.salary");System.out.println(salary);}}
内置的 PropertyEditor 实现
Spring 使用 PropertyEditor 的概念来实现对象和字符串之间的转换。用不同于对象本身的方式来表示属性是很方便的。例如,一个 Date 可以用人类可读的方式表示(如 String:'2007-14-09'),而我们仍然可以将人类可读的形式转换回原来的日期(或者,甚至更好,将任何以人类可读形式输入的日期转换回 Date 对象)。这种行为可以通过注册 java.beans.PropertyEditor类型的自定义编辑器来实现。在 BeanWrapper 上注册自定义编辑器,或者在特定的 IoC 容器中注册(如前一章中提到的),使其了解如何将属性转换为所需类型。关于 PropertyEditor 的更多信息,请参阅 Oracle 的 java.beans 包的 javadoc。
有几个在 Spring 中使用属性编辑的例子:
- 在 Bean 上设置属性是通过使用 PropertyEditor 实现完成的。当你使用 String 作为你在 XML 文件中声明的某个 Bean 的属性的值时,Spring(如果相应属性的 setter 有一个 Class 参数)会使用 ClassEditor 来尝试将该参数解析为一个 Class 对象。
- 在 Spring 的 MVC 框架中,解 HTTP 请求参数是通过使用各种 PropertyEditor 实现完成的,你可以在 CommandController 的所有子类中手动绑定。
Spring 有许多内置的 PropertyEditor 实现,使生活变得简单。它们都位于org.springframework.beans.propertyeditors包中。大多数(但不是全部,如下表所示)默认由 BeanWrapperImpl 注册。如果属性编辑器可以以某种方式配置,你仍然可以注册你自己的变体来覆盖默认的。下表描述了 Spring 提供的各种 PropertyEditor 实现:
| Class | 描述 |
|---|---|
| ByteArrayPropertyEditor | 字节数组的编辑器。将字符串转换为其相应的字节表示。默认由 BeanWrapperImpl 注册。 |
| ClassEditor | 将代表类的字符串解析为实际的类,反之亦然。当没有找到一个类时,会抛出一个 IllegalArgumentException。默认情况下,通过 BeanWrapperImpl 注册。 |
| CustomBooleanEditor | 用于布尔(Boolean)属性的可定制的属性编辑器。默认情况下,由 BeanWrapperImpl 注册,但可以通过注册它的一个自定义实例作为自定义编辑器来重写。 |
| CustomCollectionEditor | 集合的属性编辑器,将任何源集合转换为一个给定的目标集合类型。 |
| 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 对象,反之亦然(字符串格式为[language]_[country]_[variant],与 Locale 的 toString()方法相同)。也接受空格作为分隔符,作为下划线的替代。默认情况下,由 BeanWrapperImpl 注册。 |
| PatternEditor | 可以将字符串解析为 java.util.regex.Pattern对象,反之亦然。 |
| PropertiesEditor | 可以将字符串(格式为 java.util.Properties类的 javadoc 中定义的格式)转换为属性对象。默认情况下,由 BeanWrapperImpl 注册。 |
| StringTrimmerEditor | 可以修剪字符串的属性编辑器。可选择允许将空字符串转换为空值。默认情况下没有注册 - 必须由用户注册。 |
| URLEditor | 可以将一个 URL 的字符串表示解析为一个实际的 URL 对象。默认情况下,由 BeanWrapperImpl 注册。 |
Spring 使用 java.beans.PropertyEditorManager来设置可能需要的属性编辑器的搜索路径。搜索路径还包括 sun.bean.editors,其中包括字体、颜色等类型的 PropertyEditor 实现,以及大多数的原始类型。还要注意的是,如果 PropertyEditor 类与它们所处理的类在同一个包中,并且与该类的名称相同,并附加了 Editor,那么标准的 JavaBeans 基础设施就会自动发现这些类(无需你明确注册它们)。例如,我们可以有以下的类和包结构,这足以让 SomethingEditor 类被识别并作为 Something 类型属性的 PropertyEditor 使用:
comchankpopSomethingSomethingEditor // Something 类的 PropertyEditor
注意,你也可以在这里使用标准的 BeanInfo JavaBeans 机制(这里有一定程度的描述)。下面的例子使用 BeanInfo 机制将一个或多个 PropertyEditor 实例与关联类的属性显式注册。
comchankpopSomethingSomethingBeanInfo // Something 类的 BeanInfo
下面是引用的 SomethingBeanInfo 类的 Java 源代码,它将一个 CustomNumberEditor 与 Something 类的 age 属性联系起来:
package cn.mrcode.study.springdocsread.data;import org.springframework.beans.propertyeditors.CustomNumberEditor;import java.beans.IntrospectionException;import java.beans.PropertyDescriptor;import java.beans.PropertyEditor;import java.beans.SimpleBeanInfo;/*** @author mrcode*/public class SomethingBeanInfo extends SimpleBeanInfo {public PropertyDescriptor[] getPropertyDescriptors() {try {// 自定义一个 Integer 的属性编辑器,允许传入的值为空字符串final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);// 与 Something 中的 age 关联起来,PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {@Overridepublic PropertyEditor createPropertyEditor(Object bean) {return numberPE;}};return new PropertyDescriptor[]{ageDescriptor};} catch (IntrospectionException ex) {throw new Error(ex.toString());}}}
注册自定义的 PropertyEditor
当把 Bean 属性设置为字符串值时,Spring IoC 容器最终会使用标准的 JavaBeans PropertyEditor 实现来把这些字符串转换为属性的复杂类型。Spring 预先注册了一些自定义的 PropertyEditor 实现(例如,将表示为字符串的类名转换为 Class 对象)。此外,Java 的标准 JavaBeans PropertyEditor 查询机制让一个类的 PropertyEditor 被适当地命名,并与它提供支持的类放在同一个包里,这样它就能被自动找到。
如果需要注册其他的自定义 PropertyEditors,有几种机制可用。最手动的方法是使用ConfigurableBeanFactory 接口的 registerCustomEditor()方法,假设你有一个 BeanFactory 引用,这种方法通常并不方便,也不推荐。另一种(略微更方便)的机制是使用一个特殊的 Bean 工厂后置处理器,叫做 CustomEditorConfigurer。尽管你可以用 BeanFactory 的实现来使用 Bean Factory 后处理器,但 CustomEditorConfigurer 有一个嵌套的属性设置,所以我们强烈建议你在 ApplicationContext 中使用它,在那里你可以以类似于其他 Bean 的方式部署它,而且它可以被自动检测和应用
请注意,所有的 Bean 工厂和应用程序上下文都会通过使用 BeanWrapper 来处理属性转换,自动使用一些内置的属性编辑器。BeanWrapper 注册的标准属性编辑器在上一节中列出。此外,应用程序上下文还覆盖或添加额外的编辑器,以适合特定应用程序上下文类型的方式处理资源查找。
考虑下面的例子,它定义了一个名为 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 定义显示了如何设置这种关系:
<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,然后 ApplicationContext 将能够根据需要使用它。
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"><property name="customEditors"><map><!-- 这里通过 key:value 来对应关系,也就是哪一个类使用的属性编辑器是什么 --><entry key="example.ExoticType" value="example.ExoticTypeEditor"/></map></property></bean>
使用 PropertyEditorRegistrar
另一种在 Spring 容器中注册属性编辑器的机制是创建和使用 PropertyEditorRegistrar。当你需要在几种不同情况下使用同一组属性编辑器时,这个接口特别有用。你可以编写一个相应的注册器,并在每种情况下重复使用它。PropertyEditorRegistrar 实例与一个名为 PropertyEditorRegistry 的接口协同工作(注意:这两个不是一个接口),这个接口由 Spring BeanWrapper(和 DataBinder)实现。PropertyEditorRegistrar 实例在与 CustomEditorConfigurer(此处描述)一起使用时特别方便,后者暴露了一个名为 setPropertyEditorRegistrars(.)的属性。以这种方式添加到 CustomEditorConfigurer 的 PropertyEditorRegistrar 实例可以很容易地与 DataBinder 和 Spring MVC 控制器共享。此外,它避免了对自定义编辑器的同步需求。PropertyEditorRegistrar 被期望为每个 bean 创建尝试创建新的 PropertyEditor 实例。
下面的例子显示了如何创建你自己的 PropertyEditorRegistrar 实现:
package cn.mrcode.study.springdocsread.data;import org.springframework.beans.PropertyEditorRegistrar;import org.springframework.beans.PropertyEditorRegistry;/*** @author mrcode*/public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {@Overridepublic void registerCustomEditors(PropertyEditorRegistry registry) {// 预计新的 PropertyEditor 实例将被创建。registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());// 你可以根据需要在这里注册尽可能多的自定义属性编辑器...}}
请参阅 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="cn.mrcode.study.springdocsread.data.CustomPropertyEditorRegistrar"/>
最后(对于那些使用 Spring 的MVC Web 框架的人来说,这有点偏离了本章的重点),将 PropertyEditorRegistrar 与数据绑定的控制器(比如 SimpleFormController)结合起来使用会非常方便。下面的例子在 initBinder(.)方法的实现中使用了 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);}// other methods to do with registering a User}
这种 PropertyEditor 注册方式可以带来简洁的代码(initBinder(..)的实现只有一行),并且让普通的 PropertyEditor 注册代码被封装在一个类中,然后根据需要在许多控制器中共享。
