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 来获取和设置属性:

  1. package cn.mrcode.study.springdocsread.data;
  2. /**
  3. * @author mrcode
  4. */
  5. public class Company {
  6. private String name;
  7. private Employee managingDirector;
  8. public String getName() {
  9. return this.name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public Employee getManagingDirector() {
  15. return this.managingDirector;
  16. }
  17. public void setManagingDirector(Employee managingDirector) {
  18. this.managingDirector = managingDirector;
  19. }
  20. }
  1. package cn.mrcode.study.springdocsread.data;
  2. /**
  3. * @author mrcode
  4. */
  5. public class Employee {
  6. private String name;
  7. private float salary;
  8. public String getName() {
  9. return this.name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public float getSalary() {
  15. return salary;
  16. }
  17. public void setSalary(float salary) {
  18. this.salary = salary;
  19. }
  20. }

下面显示了如何操作他们属性的示例

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.beans.BeanWrapper;
  3. import org.springframework.beans.BeanWrapperImpl;
  4. import org.springframework.beans.PropertyValue;
  5. /**
  6. * @author mrcode
  7. */
  8. public class DemoTest {
  9. public static void main(String[] args) {
  10. BeanWrapper company = new BeanWrapperImpl(new Company());
  11. // 设置 name 属性的值
  12. company.setPropertyValue("name", "Some Company Inc.");
  13. // ... 也可以这样设置 name 属性的值
  14. PropertyValue value = new PropertyValue("name", "Some Company Inc.");
  15. company.setPropertyValue(value);
  16. // 创建一个员工对象,并设置到 managingDirector 字段上
  17. BeanWrapper jim = new BeanWrapperImpl(new Employee());
  18. jim.setPropertyValue("name", "Jim Stravinsky");
  19. company.setPropertyValue("managingDirector", jim.getWrappedInstance());
  20. // 通过 company 实例,获取 managingDirector 属性上的 salary 字段
  21. Float salary = (Float) company.getPropertyValue("managingDirector.salary");
  22. System.out.println(salary);
  23. }
  24. }

内置的 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 使用:

  1. com
  2. chank
  3. pop
  4. Something
  5. SomethingEditor // Something 类的 PropertyEditor

注意,你也可以在这里使用标准的 BeanInfo JavaBeans 机制(这里有一定程度的描述)。下面的例子使用 BeanInfo 机制将一个或多个 PropertyEditor 实例与关联类的属性显式注册。

  1. com
  2. chank
  3. pop
  4. Something
  5. SomethingBeanInfo // Something 类的 BeanInfo

下面是引用的 SomethingBeanInfo 类的 Java 源代码,它将一个 CustomNumberEditor 与 Something 类的 age 属性联系起来:

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.beans.propertyeditors.CustomNumberEditor;
  3. import java.beans.IntrospectionException;
  4. import java.beans.PropertyDescriptor;
  5. import java.beans.PropertyEditor;
  6. import java.beans.SimpleBeanInfo;
  7. /**
  8. * @author mrcode
  9. */
  10. public class SomethingBeanInfo extends SimpleBeanInfo {
  11. public PropertyDescriptor[] getPropertyDescriptors() {
  12. try {
  13. // 自定义一个 Integer 的属性编辑器,允许传入的值为空字符串
  14. final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
  15. // 与 Something 中的 age 关联起来,
  16. PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
  17. @Override
  18. public PropertyEditor createPropertyEditor(Object bean) {
  19. return numberPE;
  20. }
  21. };
  22. return new PropertyDescriptor[]{ageDescriptor};
  23. } catch (IntrospectionException ex) {
  24. throw new Error(ex.toString());
  25. }
  26. }
  27. }

注册自定义的 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 设置为一个属性。

  1. package example;
  2. public class ExoticType {
  3. private String name;
  4. public ExoticType(String name) {
  5. this.name = name;
  6. }
  7. }
  8. public class DependsOnExoticType {
  9. private ExoticType type;
  10. public void setType(ExoticType type) {
  11. this.type = type;
  12. }
  13. }

当事情被正确设置后,我们希望能够将类型属性分配为一个字符串,由 PropertyEditor 将其转换为一个实际的 ExoticType 实例。下面的 Bean 定义显示了如何设置这种关系:

  1. <bean id="sample" class="example.DependsOnExoticType">
  2. <property name="type" value="aNameForExoticType"/>
  3. </bean>

PropertyEditor 的实现可以类似于以下内容:

  1. // 将字符串表示转换为 ExoticType 对象
  2. package example;
  3. public class ExoticTypeEditor extends PropertyEditorSupport {
  4. public void setAsText(String text) {
  5. setValue(new ExoticType(text.toUpperCase()));
  6. }
  7. }

最后,下面的例子显示了如何使用 CustomEditorConfigurer 向 ApplicationContext 注册新的 PropertyEditor,然后 ApplicationContext 将能够根据需要使用它。

  1. <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
  2. <property name="customEditors">
  3. <map>
  4. <!-- 这里通过 key:value 来对应关系,也就是哪一个类使用的属性编辑器是什么 -->
  5. <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
  6. </map>
  7. </property>
  8. </bean>

使用 PropertyEditorRegistrar

另一种在 Spring 容器中注册属性编辑器的机制是创建和使用 PropertyEditorRegistrar。当你需要在几种不同情况下使用同一组属性编辑器时,这个接口特别有用。你可以编写一个相应的注册器,并在每种情况下重复使用它。PropertyEditorRegistrar 实例与一个名为 PropertyEditorRegistry 的接口协同工作(注意:这两个不是一个接口),这个接口由 Spring BeanWrapper(和 DataBinder)实现。PropertyEditorRegistrar 实例在与 CustomEditorConfigurer(此处描述)一起使用时特别方便,后者暴露了一个名为 setPropertyEditorRegistrars(.)的属性。以这种方式添加到 CustomEditorConfigurer 的 PropertyEditorRegistrar 实例可以很容易地与 DataBinder 和 Spring MVC 控制器共享。此外,它避免了对自定义编辑器的同步需求。PropertyEditorRegistrar 被期望为每个 bean 创建尝试创建新的 PropertyEditor 实例。

下面的例子显示了如何创建你自己的 PropertyEditorRegistrar 实现:

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.beans.PropertyEditorRegistrar;
  3. import org.springframework.beans.PropertyEditorRegistry;
  4. /**
  5. * @author mrcode
  6. */
  7. public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
  8. @Override
  9. public void registerCustomEditors(PropertyEditorRegistry registry) {
  10. // 预计新的 PropertyEditor 实例将被创建。
  11. registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
  12. // 你可以根据需要在这里注册尽可能多的自定义属性编辑器...
  13. }
  14. }

请参阅 org.springframework.beans.support.ResourceEditorRegistrar,了解 PropertyEditorRegistrar 的实现示例。注意在它实现 registerCustomEditors(.)方法时,它为每个属性编辑器创建了新的实例。

下一个例子显示了如何配置一个 CustomEditorConfigurer,并将我们的 CustomPropertyEditorRegistrar 的 实例注入其中:

  1. <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
  2. <property name="propertyEditorRegistrars">
  3. <list>
  4. <ref bean="customPropertyEditorRegistrar"/>
  5. </list>
  6. </property>
  7. </bean>
  8. <bean id="customPropertyEditorRegistrar"
  9. class="cn.mrcode.study.springdocsread.data.CustomPropertyEditorRegistrar"/>

最后(对于那些使用 Spring 的MVC Web 框架的人来说,这有点偏离了本章的重点),将 PropertyEditorRegistrar 与数据绑定的控制器(比如 SimpleFormController)结合起来使用会非常方便。下面的例子在 initBinder(.)方法的实现中使用了 PropertyEditorRegistrar:

  1. // 这个 SimpleFormController 是什么东西就不知道了,所以没法测试下去
  2. public final class RegisterUserController extends SimpleFormController {
  3. private final PropertyEditorRegistrar customPropertyEditorRegistrar;
  4. public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
  5. this.customPropertyEditorRegistrar = propertyEditorRegistrar;
  6. }
  7. protected void initBinder(HttpServletRequest request,
  8. ServletRequestDataBinder binder) throws Exception {
  9. this.customPropertyEditorRegistrar.registerCustomEditors(binder);
  10. }
  11. // other methods to do with registering a User
  12. }

这种 PropertyEditor 注册方式可以带来简洁的代码(initBinder(..)的实现只有一行),并且让普通的 PropertyEditor 注册代码被封装在一个类中,然后根据需要在许多控制器中共享。