# 3.3. Bean 操作和BeanWrapper

`org.springframework.beans`包遵循 JavaBeans 标准。JavaBean 是具有默认无参数构造函数的类，并且遵循命名约定，其中（例如）命名`bingoMadness`的属性将具有`setBingoMadness(..)` setter 方法和`getBingoMadness()` getter 方法。有关 JavaBeans 和规范的更多信息，请参阅 [javabeans](https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html)。

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

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

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

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

| 表达式                    | 备注                                                                          |
| ---------------------- | --------------------------------------------------------------------------- |
| `name`                 | 指示与 `getName()` 或 `isName()` 和`setName(..)`方法对应的属性名称。                       |
| `account.name`         | 指示对应于（例如） `getAccount().setName()` 或`getAccount().getName()`方法的属性帐户的嵌套属性名称。 |
| `account[2]`           | 指示索引属性`account`的*第三个*元素。索引属性可以是`array`、`list`或其他自然排序的集合。                    |
| `account[COMPANYNAME]` | 指示由 `account` `Map` 属性的 COMPANYNAME 键索引的映射条目的值。                             |

（如果您不打算直接使用 `BeanWrapper`，那么下一部分对您来说并不重要。如果您只使用`DataBinder`和 `BeanFactory` 以及它们的默认实现，您应该跳到关于 的[`PropertyEditors`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beans-conversion) 部分。）

以下两个示例类使用`BeanWrapper`来获取和设置属性：

```java
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;
    }
}
```

```java
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 和`Employee`s 的某些属性：

```java
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`的概念来实现 `Object`和`String`之间的转换。以与对象本身不同的方式表示属性可能很方便。例如，`Date` 可以以人类可读的方式表示（如`String`: `'2007-14-09'`），而我们仍然可以将人类可读的形式转换回原始日期（或者，更好的是，将以人类可读的形式输入的任何日期转换回`Date`对象）。这种行为可以通过注册自定义类型的编辑器`java.beans.PropertyEditor`来实现 。在一个特定的 IoC 容器中注册自定义编辑器`BeanWrapper`（如前一章所述），使其了解如何将属性转换为所需的类型。有关`PropertyEditor`更多信息 ，请[参阅`java.beans`来自 Oracle 的软件包](https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html)。

在 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`属性编辑器，例如`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 中定义的格式格式化 ）转换为`Properties`对象。默认情况下，由`BeanWrapperImpl` 注册。                                                         |
| `StringTrimmerEditor`     | 修剪字符串的属性编辑器。可选地允许将空字符串转换为`null`值。默认情况下未注册 - 必须是用户注册的。                                                                                                       |
| `URLEditor`               | 可以将 URL 的字符串表示解析为实际`URL`对象。默认情况下，由`BeanWrapperImpl` 注册。                                                                                                     |

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

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

请注意，您也可以在此处使用标准`BeanInfo` JavaBeans 机制（在[此处](https://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html)进行了一定程度的描述 ）。以下示例使用`BeanInfo`机制显式注册一个或多个 具有关联类属性的`PropertyEditor`实例：

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

被`SomethingBeanInfo`引用类的以下 Java 源代码将`CustomNumberEditor`与`Something`类的`age`属性相关联：

```java
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 容器最终使用标准 JavaBeans`PropertyEditor`实现将这些字符串转换为属性的复杂类型。Spring 预先注册了许多自定义`PropertyEditor`实现（例如，将表示为字符串的类名转换为`Class`对象）。此外，Java 的标准 JavaBeans`PropertyEditor`查找机制允许`PropertyEditor` 对类进行适当命名，并将其放置在与其提供支持的类相同的包中，以便可以自动找到它。

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

请注意，所有 bean 工厂和应用程序上下文都会自动使用许多内置属性编辑器，通过它们使用 `BeanWrapper`来处理属性转换。[上一节](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beans-conversion)列出了`BeanWrapper` 寄存器的标准属性编辑器。此外，`ApplicationContext`还覆盖或添加额外的编辑器，以便以适合特定应用程序上下文类型的方式处理资源查找。

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

考虑以下示例，它定义了一个名为`ExoticType`的用户类和另一个名为`DependsOnExoticType`的类，需要将其`ExoticType`设置为属性：

```java
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 定义显示了如何设置这种关系：

```xml
<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>
```

`PropertyEditor`实现可能类似于以下内容：

```java
// converts string representation to ExoticType object
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

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

最后，以下示例显示了如何使用`CustomEditorConfigurer`向 `ApplicationContext`注册新的 `PropertyEditor`，然后可以根据需要使用它：

```xml
<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`（ [在此处](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beans-conversion-customeditor-registration)描述）结合使用时特别方便，它公开了一个名为`setPropertyEditorRegistrars(..)`的方法. 以这种方式添加到`PropertyEditorRegistrar`的`CustomEditorConfigurer` 实例可以很容易地与`DataBinder`和 Spring MVC 控制器共享。此外，它避免了在自定义编辑器上进行同步的需要：`PropertyEditorRegistrar`预计会 为每个 bean 创建尝试创建新`PropertyEditor`实例。

以下示例显示了如何创建自己的`PropertyEditorRegistrar`实现：

```java
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`注入其中：

```xml
<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 框架的](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc)人来说，将 `PropertyEditorRegistrar`与数据绑定 Web 控制器结合使用会非常方便。以下示例在`@InitBinder` 方法的实现中使用 `PropertyEditorRegistrar`：

```java
@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` 注册代码封装在一个类中，然后根据需要在尽可能多的控制器之间共享。
