3.3. Bean 操作和BeanWrapper

org.springframework.beans包遵循 JavaBeans 标准。JavaBean 是具有默认无参数构造函数的类,并且遵循命名约定,其中(例如)命名bingoMadness的属性将具有setBingoMadness(..) setter 方法和getBingoMadness() getter 方法。有关 JavaBeans 和规范的更多信息,请参阅 javabeans

bean 包中一个非常重要的类是BeanWrapper接口及其对应的实现(BeanWrapperImpl)。正如从 javadoc 中引用的那样, BeanWrapper提供了设置和获取属性值(单独或批量)、获取属性描述符和查询属性以确定它们是可读还是可写的功能。此外,BeanWrapper还提供对嵌套属性的支持,使子属性上的属性设置可以无限深。BeanWrapper还支持添加标准 JavaBeans 的能力, PropertyChangeListenersVetoableChangeListeners无需在目标类中支持代码。最后但并非最不重要的一点是,BeanWrapper提供了对设置索引属性的支持。通常不由应用程序代码直接使用BeanWrapper,而是由 DataBinderBeanFactory.

BeanWrapper工作方式部分由其名称表示:它包装一个 bean 以对该 bean 执行操作,例如设置和检索属性。

3.3.1. 设置和获取基本和嵌套属性

设置和获取属性是通过 BeanWrappersetPropertyValue getPropertyValue 重载方法变体完成的. 有关详细信息,请参阅他们的 Javadoc。下表显示了这些约定的一些示例:

表达式备注

name

指示与 getName()isName() setName(..)方法对应的属性名称。

account.name

指示对应于(例如) getAccount().setName() getAccount().getName()方法的属性帐户的嵌套属性名称。

account[2]

指示索引属性account第三个元素。索引属性可以是arraylist或其他自然排序的集合。

account[COMPANYNAME]

指示由 account Map 属性的 COMPANYNAME 键索引的映射条目的值。

(如果您不打算直接使用 BeanWrapper,那么下一部分对您来说并不重要。如果您只使用DataBinderBeanFactory 以及它们的默认实现,您应该跳到关于 的PropertyEditors 部分。)

以下两个示例类使用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;
    }
}

以下代码片段显示了一些示例,说明如何检索和操作实例化的Company s 和Employees 的某些属性:

BeanWrapper company = new 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 = new 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");

3.3.2. 内置PropertyEditor实现

Spring 使用PropertyEditor的概念来实现 ObjectString之间的转换。以与对象本身不同的方式表示属性可能很方便。例如,Date 可以以人类可读的方式表示(如String: '2007-14-09'),而我们仍然可以将人类可读的形式转换回原始日期(或者,更好的是,将以人类可读的形式输入的任何日期转换回Date对象)。这种行为可以通过注册自定义类型的编辑器java.beans.PropertyEditor来实现 。在一个特定的 IoC 容器中注册自定义编辑器BeanWrapper(如前一章所述),使其了解如何将属性转换为所需的类型。有关PropertyEditor更多信息 ,请参阅java.beans来自 Oracle 的软件包

在 Spring 中使用属性编辑的几个示例:

  • 在 bean 上设置属性是通过使用PropertyEditor实现来完成的。当您使用String在 XML 文件中声明的某个 bean 的属性值时,Spring(如果相应属性的设置器有Class 参数)ClassEditor会尝试将参数解析为Class对象。

  • 在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种PropertyEditor实现来完成的,您可以在 CommandController.

Spring 有许多内置的PropertyEditor实现来简化生命周期。它们都位于org.springframework.beans.propertyeditors 包中。大多数(但不是全部,如下表所示)默认情况下由 BeanWrapperImpl注册. 如果可以以某种方式配置属性编辑器,您仍然可以注册自己的变体来覆盖默认变体。下表描述了Spring PropertyEditor提供的各种实现:

备注

ByteArrayPropertyEditor

字节数组的编辑器。将字符串转换为其对应的字节表示。默认注册为BeanWrapperImpl

ClassEditor

将表示类的字符串解析为实际类,反之亦然。当找不到类时,抛出一个IllegalArgumentException。默认情况下,由 BeanWrapperImpl注册 。

CustomBooleanEditor

可自定义的Boolean属性编辑器。默认情况下,注册者为BeanWrapperImpl, 但可以通过将其自定义实例注册为自定义编辑器来覆盖。

CustomCollectionEditor

集合的属性编辑器,将任何Collection源转换为给定的目标 Collection类型。

CustomDateEditor

可定制的java.util.Date属性编辑器,支持自定义DateFormat。默认未注册。必须根据需要使用适当的格式进行用户注册。

CustomNumberEditor

任何子类的可定制Number属性编辑器,例如IntegerLongFloatDouble. 默认情况下,注册者为BeanWrapperImpl,但可以通过将其自定义实例注册为自定义编辑器来覆盖。

FileEditor

将字符串解析为java.io.File对象。默认情况下,由BeanWrapperImpl注册 。

InputStreamEditor

单向属性编辑器,可以接受一个字符串并产生(通过一个中间ResourceEditorResourceInputStream,以便InputStream 可以将属性直接设置为字符串。请注意,默认用法不会为您关闭InputStream。默认情况下,由 BeanWrapperImpl注册。

LocaleEditor

可以将字符串解析为Locale对象,反之亦然(字符串格式为 [language]_[country]_[variant],与Locale 的方法toString() 相同)。也接受空格作为分隔符,作为下划线的替代。默认情况下,由 BeanWrapperImpl注册。

PatternEditor

可以将字符串解析为java.util.regex.Pattern对象,反之亦然。

PropertiesEditor

可以将java.util.Properties字符串(使用类的 javadoc 中定义的格式格式化 )转换为Properties对象。默认情况下,由BeanWrapperImpl 注册。

StringTrimmerEditor

修剪字符串的属性编辑器。可选地允许将空字符串转换为null值。默认情况下未注册 - 必须是用户注册的。

URLEditor

可以将 URL 的字符串表示解析为实际URL对象。默认情况下,由BeanWrapperImpl 注册。

Spring 使用java.beans.PropertyEditorManager为可能需要的属性编辑器设置搜索路径。搜索路径还包括sun.bean.editors,其中包括诸如FontColor和大多数基本类型的PropertyEditor实现。另请注意,标准 JavaBeans 基础结构会自动发现PropertyEditor类(无需显式注册它们),前提是它们与它们处理的类在同一个包中并且与该类具有相同的名称,并带有Editor后缀。例如,可以具有以下类和包结构,这足以使SomethingEditor该类被识别并用作Something类型的属性的PropertyEditor

com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class

请注意,您也可以在此处使用标准BeanInfo JavaBeans 机制(在此处进行了一定程度的描述 )。以下示例使用BeanInfo机制显式注册一个或多个 具有关联类属性的PropertyEditor实例:

com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class

SomethingBeanInfo引用类的以下 Java 源代码将CustomNumberEditorSomething类的age属性相关联:

public class SomethingBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
                @Override
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                }
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}

注册其他自定义PropertyEditor实现

当将 bean 属性设置为字符串值时,Spring IoC 容器最终使用标准 JavaBeansPropertyEditor实现将这些字符串转换为属性的复杂类型。Spring 预先注册了许多自定义PropertyEditor实现(例如,将表示为字符串的类名转换为Class对象)。此外,Java 的标准 JavaBeansPropertyEditor查找机制允许PropertyEditor 对类进行适当命名,并将其放置在与其提供支持的类相同的包中,以便可以自动找到它。

如果需要注册其他自定义PropertyEditors,可以使用多种机制。最手动的方法,通常不方便或不推荐,是使用 ConfigurableBeanFactory接口的registerCustomEditor()方法,假设您有参考BeanFactory。另一种(稍微方便一点)机制是使用一个特殊的 bean factory 后处理器,称为CustomEditorConfigurer. 尽管您可以将 bean factory 后处理器与BeanFactory实现一起使用,但CustomEditorConfigurer具有嵌套属性设置,因此我们强烈建议您将其与 ApplicationContext应用。

请注意,所有 bean 工厂和应用程序上下文都会自动使用许多内置属性编辑器,通过它们使用 BeanWrapper来处理属性转换。上一节列出了BeanWrapper 寄存器的标准属性编辑器。此外,ApplicationContext还覆盖或添加额外的编辑器,以便以适合特定应用程序上下文类型的方式处理资源查找。

标准 JavaBeansPropertyEditor实例用于将表示为字符串的属性值转换为属性的实际复杂类型。您可以使用 bean 工厂后处理器CustomEditorConfigurer,方便地将对其他PropertyEditor实例的支持添加到ApplicationContext.

考虑以下示例,它定义了一个名为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;
    }
}

正确设置后,我们希望能够将 type 属性分配为字符串,然后将其PropertyEditor转换为实际 ExoticType实例。以下 bean 定义显示了如何设置这种关系:

<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 {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

最后,以下示例显示了如何使用CustomEditorConfigurerApplicationContext注册新的 PropertyEditor,然后可以根据需要使用它:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>

使用PropertyEditorRegistrar

向 Spring 容器注册属性编辑器的另一种机制是创建和使用PropertyEditorRegistrar. 当您需要在几种不同的情况下使用同一组属性编辑器时,此接口特别有用。您可以编写相应的注册器并在每种情况下重复使用它。 PropertyEditorRegistrar实例与名为 PropertyEditorRegistry的接口一起工作,该接口由 Spring BeanWrapper (and DataBinder) 实现。PropertyEditorRegistrar实例与CustomEditorConfigurer在此处描述)结合使用时特别方便,它公开了一个名为setPropertyEditorRegistrars(..)的方法. 以这种方式添加到PropertyEditorRegistrarCustomEditorConfigurer 实例可以很容易地与DataBinder和 Spring MVC 控制器共享。此外,它避免了在自定义编辑器上进行同步的需要:PropertyEditorRegistrar预计会 为每个 bean 创建尝试创建新PropertyEditor实例。

以下示例显示了如何创建自己的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 框架的人来说,将 PropertyEditorRegistrar与数据绑定 Web 控制器结合使用会非常方便。以下示例在@InitBinder 方法的实现中使用 PropertyEditorRegistrar

@Controller
public class RegisterUserController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    @InitBinder
    void initBinder(WebDataBinder binder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods related to registering a User
}

这种注册PropertyEditor风格可以带来简洁的代码(@InitBinder方法的实现只有一行长),并且可以将通用的PropertyEditor 注册代码封装在一个类中,然后根据需要在尽可能多的控制器之间共享。

最后更新于