示例代码
参考文档
- Spring 官方文档
- 小马哥:Spring 核心编程思想
涉及问题
- 什么是数据绑定(
Data Binding
)? -
DataBinder
与BeanWrapper
之间的关系? -
BeanWrapper
都具备哪些功能 ? - 数据绑定过程中的类型转换(
TypeConvert
)是怎么实现的?
DataBinder
- 什么是数据绑定呢?
Spring 官方文档:
Data binding is useful for letting user input be dynamically bound to the domain model of an application (or whatever objects you use to process user input). Spring provides the aptly named
DataBinder
to do exactly that. TheValidator
and theDataBinder
make up thevalidation
package, which is primarily used in but not limited to the web layer.
由此可知,数据绑定就是将用户输入绑定到领域对象模型的过程。通常是指将用户提供的一系列属性与领域对象的 Fields
进行绑定并赋值的过程。
对于用户提供的这些属性,spring
提供了一个 PropertyValue
类来进行封装。其中 name
表示属性名称,value
则表示该属性对应的值,convertedValue
则用来存储经过转化后的值(如果有必要的话)。
public class PropertyValue extends BeanMetadataAttributeAccessor
implements Serializable {
private final String name;
@Nullable
private final Object value;
@Nullable
private Object convertedValue;
// ...省略部分代码
}
DataBinder
对此,spring
提供了一个 DataBinder
类来提供数据绑定功能。它除了提供了将 PropertyValue
绑定到目标对象 target
的功能外,还提供了Validator
相关的数据校验功能。不过此篇我们暂且忽略数据校验部分,而是先把重心放在数据绑定部分。
我们先通过一段简单的代码来对 DataBinder
类有一个直观的了解:
public static void main(String[] args) {
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("id", 10);
propertyValues.addPropertyValue("name", " jerry");
propertyValues.addPropertyValue("age", 18);
dataBinder.bind(propertyValues);
System.out.println(user);
}
这段代码的输出结果为:
User{id=10, name='jerry', age=18}
MutablePropertyValues
内置了一个 PropertyValue
的集合。我们可以通过 DataBinder
的 bind(propertyValues)
方法来将一个或多个属性值绑定到目标对象上。
通过对 bind
方法进行 debug
分析,我们发现实际上 DataBinder
调用的是一个更底层的类 BeanWrapperImpl
来实现对 Bean
的操作的。
BeanWrapper
:一个底层的 Bean 操作类
The
BeanWrapper
is a fundamental concept in the Spring Framework and is used in a lot of places. However, you probably do not need to use theBeanWrapper
directly.
如官方文档所言,BeanWrapper
作为一个 spring
框架的一个底层基础工具(通常并不需要直接去使用它),运用于多个地方。例如:DataBinder
以及 BeanFactory
。
接下来,我们便对 BeanWrapper
进行一个详细的分析:
首先,我们先看一下 BeanWrapper
的默认实现类 BeanWrapperImpl
的类继承结构图:
我们重点关注这三个接口,先对它们有一个直观的了解,然后再逐个分析:
BeanWrapper
提供了包装 Bean
的能力,以及对所封装 Bean
的自省能力。(自省是指自我观察获取自身内部结构,自我描述的能力)
PropertyAccessor
提供了直观的操作属性的能力。
TypeConverter
提供了类型转换的能力。
那么,总结一下,BeanWrapperImpl
是一个可以包装一个目标对象,并可以对目标对象进行自省以及属性赋值的工具类。并且在属性赋值的过程中支持属性值的类型转换。那么 BeanWrapperImpl
便具备以下能力:
- 自省能力
- 属性存取能力
- 类型转化能力
下面呢,将会对这些能力的实现逐一进行更详细的分析。
Bean Introspection
: 自省能力
Bean Introspection
是指 Bean
的自省。
The
org.springframework.beans
package adheres to the JavaBeans standard. A JavaBean is a class with a default no-argument constructor and that follows a naming convention where (for example) a property namedbingoMadness
would have a setter methodsetBingoMadness(..)
and a getter methodgetBingoMadness()
. For more information about JavaBeans and the specification, see javabeans.
Spring
的 org.springframework.beans
包遵守了 JavaBeans
标准。一个 JavaBean
呢,是指一个具备默认构造函数,并且遵守一定命名规约的类。比如,我们平日里熟知的 getter
和 setter
方法,则分别对应了相关属性的读方法和写方法。
在 java.beans
包下,提供了一个 Introspector
工具类,提供了对 JavaBean
的自省能力,通过Introspector.getBeanInfo(Class clazz)
方法,可以返回一个自省结果 BeanInfo
对象。通过该对象可以获取属性的描述 PropertyDescriptor
,方法的描述 MethodDescriptor
以及Bean
的描述 BeanDescriptor
。
通过 PropertyDescriptor
则可以获取对应属性的类型,以及相应的读写方法。
而 BeanWrapperImpl
提供的对所包装 Bean
的自省能力,实际上也是来源于 java.beans.Introspector
。
在 BeanWrapperImpl
类中,存在这样一个成员变量:CachedIntrospectionResults
。
通过观察 CachedIntrospectionResults
可以看出,它也是调用 Introspector
来对类进行自省。然后基于此并做了相应的缓存处理。
private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanClass);
if (beanInfo != null) {
return beanInfo;
}
}
return (shouldIntrospectorIgnoreBeaninfoClasses ?
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
Introspector.getBeanInfo(beanClass));
}
至此,我们的 BeanWrapperImpl
已经具备了一定的自省能力。
private static void main(String[] args) {
BeanWrapper beanWrapper = new BeanWrapperImpl(User.class);
PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
System.out.printf("%s : %s, %s\n", propertyDescriptor.getName(),
propertyDescriptor.getReadMethod(),
propertyDescriptor.getWriteMethod());
}
}
输出结果:
age : public java.lang.Integer com.grasswort.beans.model.User.getAge(), public void com.grasswort.beans.model.User.setAge(java.lang.Integer)
class : public final native java.lang.Class java.lang.Object.getClass(), null
id : public java.lang.Long com.grasswort.beans.model.User.getId(), public void com.grasswort.beans.model.User.setId(java.lang.Long)
name : public java.lang.String com.grasswort.beans.model.User.getName(), public void com.grasswort.beans.model.User.setName(java.lang.String)
PropertyAccessor
:属性存取能力
Java Doc:
Common interface for classes that can access named properties (such as bean properties of an object or fields in an object) . Serves as base interface for {@link BeanWrapper}.
PropertyAccessor
作为 BeanWrapper
的一个基本接口,定义了一系列对命名属性的访问和存储方法。
- 判断属性是否可读,可写
isReadableProperty
isWritableProperty
- 获取属性的类型
getPropertyType
getPropertyTypeDescriptor
- 属性值的获取
getPropertyValue
- 设置属性值
setPropertyValue
setPropertyValues
通过这些方法,我们可以直接地去操作 Bean
的属性。
private static void main(String[] args) {
BeanWrapper beanWrapper = new BeanWrapperImpl(User.class);
beanWrapper.setPropertyValue("id", "1");
beanWrapper.setPropertyValue("name", "jerry");
System.out.println("id : " + beanWrapper.getPropertyValue("id"));
System.out.println("name : " + beanWrapper.getPropertyValue("name"));
System.out.println(beanWrapper.getWrappedInstance());
}
输出结果:
id : 1
name : jerry
User{id=1, name='jerry', age=null}
TypeConverter
:类型转换能力
以上,我们已经了解到,我们可以通过 BeanWrapper
的 setPropertyValue
方法来设置属性的值。但是上文示例代码中使用的是:
beanWrapper.setPropertyValue("id", "1");
而在 User.java
中,id
声明的却是 Long
类型:
private Long id;
而之所以能够设置属性值成功,则说明在赋值的过程中,一定存在一个类型转换的过程。
BeanWrapperImpl
之所以具备类型转换能力,是因为它继承自 TypeConverterSupport
。TypeConverterSupport
组合了一个 TypeConverterDelegate
委派对象,类型转换逻辑将交由 TypeConvertDelegate
去执行操作。
为了使头脑中对此处结构更加清晰,特意对 BeanWrapperImpl
类图进行了一次重新整理:
在图中心偏上位置的是一个 PropertyEditorRegistrySupport
类。它提供了 PropertyEditorRegistry
的具体实现,支持对基于 JavaBeans
由 jdk1.1
提供的类型转换接口 PropertyEditor
进行注册和扩展。除此之外,它还包含了一个可选的(@Nullable
) ConversionService
的成员变量,提供了对 spring 3
的一系列spring
自己提供的类型转换接口的支持与扩展。
-
基础的
PropertyEditor
-
可选的
ConversionService
而具体的转换逻辑,则在 TypeConvertDelegate
类中进行了体现。
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 先尝试寻找自定义的 PropertyEditor
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
// 如果没有找到对应的 PropertyEditor,并且 ConversionService 不为空。则尝试使用 ConversionService 去转换。
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
// ...
}
Object convertedValue = newValue;
// 如果找到了相应的 PropertyEditor
// 或者没找到,但是传入类型与要求类型不符,则寻找默认 PropertyEditor 进行转换
if (editor != null
|| (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
// ...
}
// 如果还没转换成功,则尝试应用一些标准类型转换规则。
// 包含数字、枚举、数组、集合等的处理。
}
OK,那么我们已经了解到类型转换可以有两种扩展方式:
- 基于
JavaBeans
的PropertyEditor
-
Spring 3
提供的core.convert
包下的新接口,包含Converter<S, T>
、GenericConverter
、ConverterFactory<S, T>
PropertyEditor
:基于 JavaBeans
的类型转换接口
PropertyEditor
位于 java.beans
包下,支持将 String
类型的对象转换成其他任意类型的对象。
通常我们是通过 setAsText(String text)
方法来传入一个字符串,然后经过自定义的处理逻辑后调用 setValue(Object obj)
方法,将转换后的值存储起来,客户端便可以通过 getValue()
方法获取转换后的值。
另外调用setValue(Object obj)
方法的时候会触发一个PropertyChangeEvent
事件,通知所有监听的 PropertyChangeListener
。
通常,我们创建一个自定义的 PropertyEditor
时,并不需要直接实现 PropertyEditor
接口。而是通过继承 PropertyEditorSupport
类,然后选择性地重写 setAsText(String text)
和 getAsText()
方法即可。
例如:
public class StringToUserPropertyEditor extends PropertyEditorSupport {
// 例如: 1-jerry-8
@Override
public String getAsText() {
User user = (User) getValue();
return user.getId() + "-" + user.getName() + "-" + user.getAge();
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] strArray = text.split("-");
User user = new User();
user.setId(Long.valueOf(strArray[0]));
user.setName(strArray[1]);
user.setAge(Integer.valueOf(strArray[2]));
setValue(user);
}
public static void main(String[] args) {
String text = "1-jerry-8";
StringToUserPropertyEditor editor = new StringToUserPropertyEditor();
editor.setAsText(text);
User user = (User) editor.getValue();
System.out.println(user);
}
}
输出结果为:
User{id=1, name='jerry', age=8}
PropertyEditor
虽然具备了类型转换的能力,却也存在着一定的局限性。
- 它的来源类型只能是
String
类型。 - 它的实现缺少类型安全,实现类无法感知目标转换类型。
- 它的实现使用了成员变量
value
来存储转换结果,线程不安全。 - 除了类型转换为,它的实现了还包含了
JavaBeans
时间以及JavaGUI
交互(在以上的UML
类图中省略了这部分方法)。违反了职责单一原则。
因此, Spring 3
又提供了一些新的类型转换接口支持。
Spring 3 core.convert
通用类型转换
Convert<S, T>
在 Spring 3
之后,提供了这样一个接口 Convert<S, T>
。
S
代表了来源数据类型, T
则代表了目标转换类型。通过 Java
泛型限制了方法的参数类型和返回类型。解决了 PropertyEditor
的类型不安全问题,同时 T convert(S source)
的方法设计,相比于 PropertyEditor
需要先存储后取值的接口方法设计,更有助于其实现类实现一个线程安全的类型转换器。
该接口实现非常的简单,且容易理解,便不再举例说明。
但它仍然存在一些小的问题,由于泛型擦除的原因,当我们注册了许多 Converter
的时候,我们不可能去一一进行转换尝试。
所以 Spring
又提供了一个 ConditionalConvert
接口。
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
我们的实现类可以同时实现这两个接口:Converter
,ConditionalConverter
。在转化过程中会先去调用 matches
方法,来判断是否匹配,匹配的话才会调用 convert
方法进行转换。
public class PropertiesToUserConverter implements Converter<Properties, User>, ConditionalConverter {
@Override
public User convert(Properties source) {
User user = new User();
user.setId(Long.valueOf(source.getProperty("id", "0L")));
user.setName(String.valueOf(source.getOrDefault("name", "")));
user.setAge(Integer.valueOf(source.getProperty("age", "0")));
return user;
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return sourceType.getObjectType().isAssignableFrom(Properties.class)
&& targetType.getObjectType().isAssignableFrom(User.class);
}
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("id", "1");
properties.setProperty("name", "jerry");
properties.setProperty("age", "8");
PropertiesToUserConverter converter = new PropertiesToUserConverter();
boolean matched = converter.matches(TypeDescriptor.forObject(properties), TypeDescriptor.valueOf(User.class));
if (matched) {
User user = converter.convert(properties);
System.out.println(user);
}
}
}
GenericConverter
Spring
还提供了一个支持复杂类型转换的接口:GenericConverter
。它相比于 Converter<S, T>
更加灵活。
可以通过 getConvertibleTypes()
返回一个 ConvertiblePair
集合。每一个 ConvertiblePair
都包含了一个 soureType
和 targetType
,表示可转换类型对。
GenericConverter
可以与 Converter
相配合,对复杂的数据类型进行转换。通常可以用于对集合类型的对象进行转换 。
具体可以查看相应的实现类来查看。
Spring 官方文档:
Because
GenericConverter
is a more complex SPI interface, you should use it only when you need it. FavorConverter
orConverterFactory
for basic type conversion needs.
不过,GenericConverter
作为通用类型转换器虽然功能强大,但只有需要的时候才去使用它。如果可以,还是要优先考虑 Converter
或者 ConverterFactory
。
ConversionService
在 core.convert
包下,还存在这样一个类 ConversionService
。
public interface ConversionService {
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
@Nullable
<T> T convert(@Nullable Object source, Class<T> targetType);
@Nullable
Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}
A
ConversionService
is a stateless object designed to be instantiated at application startup and then shared between multiple threads. In a Spring application, you typically configure aConversionService
instance for each Spring container (orApplicationContext
). Spring picks up thatConversionService
and uses it whenever a type conversion needs to be performed by the framework. You can also inject thisConversionService
into any of your beans and invoke it directly.
JavaDoc :
A service interface for type conversion. This is the entry point into the convert system.
Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
如 JavaDoc
所言,这是一个类型转换服务入口类(Spring 3 core.convert
)。通常,我们使用这个类来做类型转换即可。
它的实现类实现了 ConverterRegistry
接口,支持 Converter
、ConverterFactory
、GenericConverter
的注册与扩展。
在 Spring
应用程序中,我们可以通过 ConversionServiceFactoryBean
来将 ConversionService
注册到容器中。但是需要注意的是 id
必须为 conversionService
。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
网友评论