# 3.3bean操作和BeanWrapper

org.springframework.beans 包遵循JavaBeans标准。一个JavaBeans类拥有默认的无参构造函数，和下面的命名约定，例如：一个命名为bingoMadness的属性，应该有一个setter方法：setBingoMadness(..) 和一个getter方法：getBingoMadness()。

beans 包里面有个很重要的类叫做BeanWrapper接口和他的实现BeanWrapperImpl。正如JavaDoc所引用的，BeanWrapper提供了设置和获取属性值（单个或批量）、获取属性描述符和查询属性的功能，以确定它们是可读的还是可写的。此外，BeanWrapper还支持嵌套属性，允许将子属性的属性设置为无限深度。BeanWrapper还支持添加标准JavaBeans属性PropertyChangeListeners和VetoableChangeListeners，而不需要在目标类中支持代码。最后，BeanWrapper提供了对设置索引属性的支持。BeanWrapper通常不直接由应用程序代码使用，而是由DataBinder和BeanFactory使用。

## 3.3.1 设置和获取基本的和内嵌的属性

设置和获取属性可以通过setPropertyValue，setPropertyValues，getPropertyValue和getPropertyValues方法，和一系列的可覆盖的变种。Springs 的javadoc 有详细描述。 JavaBeans规范对标记对象的属性有专门的约定：

| Expression            | Explanation                                                            |
| --------------------- | ---------------------------------------------------------------------- |
| name                  | 指示属性name相对于的getName() ， isName() 和 setName(..)方法                       |
| account.name          | 指示account内部的name属性，对应getAccount().setName() 和getAccount().getName()方法。 |
| account\[2]           | 指示account的第三个元素，索引属性可以是array, list或者其他的自然排序的集合。                        |
| account\[COMPANYNAME] | 一个map，key是COMPANYNAMEs                                                 |

（如果你不打算直接使用BeanWrapper，那么下一节对你来说并不重要。如果只使用DataBinder和BeanFactory及其默认实现，则应跳到PropertyEditors部分。）

下面的例子使用BeanWrapper来get和set属性：

```
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());
// 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之间的转换。用不同于对象本身的方式表示属性很方便。例如，日期可以以人类可读的方式表示（字符串：“2007-14-09”），而我们仍然可以将人类可读的形式转换回原始日期（或者，更好的方法是，将以人类可读的形式输入的任何日期转换回日期对象）。此行为可以通过注册java.beans.PropertyEditor类型的自定义编辑器来实现。在BeanWrapper上注册自定义编辑器，或者在特定的ioc容器（如前一章所述）中注册自定义编辑器，可以使它了解如何将属性转换为所需的类型。有关PropertyEditor的更多信息，请参阅Oracle中java.beans包的javadoc。

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

* 通过使用PropertyEditor实现来设置bean的属性。当使用String作为在XML文件中声明的某个bean的属性值时，Spring（如果相应属性的setter有类参数）则使用ClassEditor尝试将参数解析为Class对象。
* 在Spring的MVC框架中解析HTTP请求参数是通过使用各种属性编辑器实现来完成的，这些实现可以手动绑定到CommandController的所有子类中。

Spring有内置的方便使用的PropertyEditor实现，他们都在org.springframework.beans.propertyeditors包里面，大多数都是通过BeanWrapperImpl来注册的。虽然这些property editor 在某些方面是可配置的，你也可以注册自定义的property editor来覆盖默认配置。下表列出了Spring提供的多种PropertyEditor：

| Class                   | Explanation                                                                                                                                    |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| ByteArrayPropertyEditor | 字节数组编辑器。将字符串转换为相应的字节表示形式。默认情况下由BeanWrapperImpl注册。                                                                                              |
| ClassEditor             | 将表示类的字符串解析为实际类，反之亦然。当找不到类时，将引发IllegalArgumentException。默认情况下，由BeanWrapperImpl注册。                                                               |
| CustomBooleanEditor     | 布尔属性的可自定义属性编辑器。默认情况下，由BeanWrapperImpl注册，但可以通过将其自定义实例注册为自定义编辑器来重写。                                                                              |
| CustomCollectionEditor  | 集合的属性编辑器，将任何源集合转换为给定的目标集合类型。                                                                                                                   |
| CustomDateEditor        | java.util.date的可自定义属性编辑器，支持自定义日期格式。默认情况下未注册。必须根据需要使用适当的格式进行用户注册。                                                                               |
| CustomNumberEditor      | 任何数字子类（如integer、long、float或double）的可自定义属性编辑器。默认情况下，由BeanWrapperImpl注册，但可以通过将其自定义实例注册为自定义编辑器来重写。                                                |
| FileEditor              | 将String解析成为java.io.File对象。默认由BeanWrapperImpl来注册。                                                                                               |
| InputStreamEditor       | 单向属性编辑器，它可以获取一个字符串并（通过中间的ResourceEditor和Resource）生成一个InputStream，以便InputStream属性可以直接设置为字符串。请注意，默认用法不会为你关闭inputstream。默认情况下，由BeanWrapperImpl注册。 |
| LocaleEditor            | 可以将字符串解析为区域设置对象，反之亦然（字符串格式为\[country]\[variant]，与区域设置的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实现。还要注意，如果标准JavaBeans基础结构与它们处理的类位于同一个包中，并且与该类具有相同的名称，并且附加了Editor，那么标准JavaBeans基础结构会自动发现PropertyEditor类（无需显式注册）。例如，可以具有以下类和包结构，这足以使SomethingEditor类被识别并用作某个类型化属性的属性编辑器。

```
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源代码将CustomNumberEditor与Something类的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) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}
```

**注册自定义PropertyEditor实现**

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

如果需要注册其他自定义PropertyEditors，可以使用几种机制。通常不方便或不推荐使用的最手动的方法是使用ConfigurableBeanFactory接口的registerCustomEditor（）方法，前提是你有BeanFactory引用。

另一种（稍微方便一点）机制是使用一个特殊的bean工厂后处理器，称为CustomEditorConfigurer。尽管你可以将bean工厂后处理器与BeanFactory实现结合使用，但CustomEditorConfigurer有一个嵌套的属性设置，因此我们强烈建议你将其与ApplicationContext结合使用，在这里你可以以类似于任何其他bean的方式部署它，并且在那里它可以自动化。检测并应用。

注意，所有bean工厂和应用程序上下文都自动使用许多内置的属性编辑器，通过使用BeanWrapper来处理属性转换。BeanWrapper注册器的标准属性编辑器在前一节中列出。此外，ApplicationContexts还重写或添加其他编辑器，以便以适合特定应用程序上下文类型的方式处理资源查找。

标准JavaBeans PropertyEditor实例用于将表示为字符串的属性值转换为属性的实际复杂类型。你可以使用bean工厂后处理器CustomEditorConfigurer，方便地向应用程序上下文添加对其他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;
    }
}
```

正确设置之后，我们希望能够将类型属性指定为字符串，然后由属性编辑器将其转换为实际的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()));
    }
}
```

下面例子展示了怎么使用CustomEditorConfigurer将新创建的PropertyEditor注册到ApplicationContext：

```
<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的接口一起工作，后者是由SpringBeanWrapper（和DataBinder）实现的接口。当与CustomEditorConfigurer（此处描述）结合使用时，PropertyEditorRegistrar实例特别方便，后者公开了一个名为setPropertyEditorRegistrars（..）的属性。以这种方式添加到CustomEditorConfigurer的PropertyEditorRegistrar实例可以很容易地与DataBinder和SpringMVC控制器共享。此外，它还避免了在自定义编辑器上同步的需要：一个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框架的用户来说，稍微偏离本章的重点），将PropertyEditorRegistrars与数据绑定控制器（如SimpleFormController）结合使用非常方便。以下示例在initBinder（..）方法的实现中使用了PropertyEditorRegistrar：

```
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注册代码封装在一个类中，然后根据需要在多个Controllers之间共享。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/3.validation-databinding-typeconversion/3.3beanwrapper.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
