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 使用:
com
chank
pop
Something
SomethingEditor // Something 类的 PropertyEditor
注意,你也可以在这里使用标准的 BeanInfo JavaBeans 机制(这里有一定程度的描述)。下面的例子使用 BeanInfo 机制将一个或多个 PropertyEditor 实例与关联类的属性显式注册。
com
chank
pop
Something
SomethingBeanInfo // 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) {
@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 工厂后置处理器,叫做 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 {
@Override
public 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 注册代码被封装在一个类中,然后根据需要在许多控制器中共享。