前言
首次接触到```java.beans.PropertyEditor```这个类可能是在Spring中遇到,主要的场景如下:-
在xml中定义bean时设置类的属性时传入的是一个字符串,在最后生成类对象的时候是需要转换为对应类型的。
-
在web程序中,通过网络传递的数据也需要通过这类编辑器进行转换
下面会首先谈谈JDK中这个类的使用,然后再分析在Spring中使用的姿势。
JDK中的PropertyEditor
其实这个类是属于jdk中的。在jdk中通过java.beans.PropertyEditorManager
来对这些属性编辑器进行注册和查找。
比如如下代码
PropertyEditor propertyEditor = PropertyEditorManager.findEditor(int.class);
propertyEditor.setAsText("15");
Object editorValue = propertyEditor.getValue();
Class<?> aClass = editorValue.getClass();
获取一个int类型的属性编辑器,然后将字符串15转换为Integer的对象(自动装箱操作)。
以上操作之所以能完成就是因为在jdk的包com.sun.beans.editors
中已经定义好了部分简单java类型的属性编辑器。
然后在实例化com.sun.beans.finder.PropertyEditorFinder
的时候进行了注册。
通过PropertyEditorManager
根据类型查找指定的属性编辑器的时候依次会使用到ThreadGroupContext
和PropertyEditorFinder
两个类。最终查找其实就是靠PropertyEditorFinder
这个类来完成的。
除了以上默认注册的属性编辑器,其实PropertyEditorFinder
还支持另外两种方式:
- 与属性类型在同一个包,以
Editor
结尾的类,比如查找foo.bah.Foz
时如果没有找到已经注册的,就会查到类foo.bah.FozEditor
。
假设存在以下类
package com.example.validation;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
执行以下代码
PropertyEditor propertyEditor = PropertyEditorManager.findEditor(ExoticType.class);
propertyEditor.setAsText("xiao.ming");
Object editorValue = propertyEditor.getValue();
Class<?> aClass = editorValue.getClass();
此时就无法查找到指定的属性编辑器了。
在该类包内定义如下类(重点:同一个包)
package com.example.validation;
import java.beans.PropertyEditorSupport;
public class ExoticTypeEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
此时结果如下
2. 如果以上方式还查找不到可以在指定的standardEditorsPackage
包中查找名称为FozEditor
类。这个standardEditorsPackage
默认情况是sun.beans.editors
(在PropertyEditorFinder实例化的时候设置的)。当然可以通过java.beans.PropertyEditorManager#setEditorSearchPath
来设置。通过java.beans.PropertyEditorManager#getEditorSearchPath
来访问当前配置。
如果将上面自定义的属性编辑器移到另一个包,比如com.example.validation.editor.ExoticTypeEditor
。
此时执行上面代码仍然找不到。我们可以通过setEditorSearchPath来保证可用。
String[] defSearchPath = PropertyEditorManager.getEditorSearchPath();
PropertyEditorManager.setEditorSearchPath(new String[]{"com.example.validation.editor"});
String[] newSearchPath = PropertyEditorManager.getEditorSearchPath();
结果显示现在又可以了。以上就是JDK中默认的PropertyEditor
查找的三种方式
- 已经注册的编辑器
- 类名+Editor所代表的类
- 查找路径+简单类型+Editor所代表的类
对源码感兴趣可以参考源码:com.sun.beans.finder.PropertyEditorFinder
#find和com.sun.beans.finder.InstanceFinder#find
Spring中的PropertyEditor
Spring中仍然使用JDK中相同的类,但是并没有全部按照JDK的一套。参考org.springframework.beans.BeanUtils#findEditorByConvention
这个方法。从方法名可以知道,按照传统方式来查找属性编辑器,所谓的传统方式就是JDK的方式(PropertyEditorManager
).
从以上注释,可以了解到仍然支持PropertyEditorManager
中查找同一个包内属性编辑器的规则,但是不使用默认注册的那些primitive types所对应的编辑器了。核心源码如下
String editorName = targetType.getName() + "Editor";
try {
Class<?> editorClass = cl.loadClass(editorName);
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
if (logger.isInfoEnabled()) {
logger.info("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
}
unknownEditorTypes.add(targetType);
return null;
}
return (PropertyEditor) instantiateClass(editorClass);
}
当然Spring不仅仅如此,Spring中执行与PropertyEditorManager
相同工作的是org.springframework.beans.PropertyEditorRegistry
。并以此建立了更庞大的体系。不仅仅是属性编辑器的注册与获取、属性转换、还包括了属性访问PropertyAccessor
、数据的绑定DataBinder
等等。
处于C位的是org.springframework.beans.PropertyEditorRegistrySupport
这个类。管理着默认的属性编辑器和自定义属性编辑器。在方法中org.springframework.beans.PropertyEditorRegistrySupport#createDefaultEditors
中注册了绝大部分常见类型的属性编辑器。
其中就包含了基本类型的,这也是为什么要放弃JDK中默认的属性编辑器的原因,因为Spring进行了扩展。
Spring中使用属性编辑器的一大场景就是在xml中定义的值转换为指定类型的属性时需要使用
假如有以下两个类
package com.example.validation;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
package com.example.validation;
public class DependsOnExoticType {
private ExoticType type;
public ExoticType getType() {
return type;
}
public void setType(ExoticType type) {
this.type = type;
}
}
比如定义如下bean定义
<bean id="sample" class="com.example.validation.DependsOnExoticType">
<property name="type" value="com.example.validation.Person"/>
</bean>
编写以下代码
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
DependsOnExoticType sample = (DependsOnExoticType) applicationContext.getBean("sample");
从以上执行结果不难看出将字符串转换为ExoticType
实例设置到了DependsOnExoticType
实例当中。这里是怎么做到的呢?在创建一个bean的过程中,会进行属性的填充,并转换为对应的类型。对应源码为
org.springframework.beans.TypeConverterDelegate#convertIfNecessary
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue,
Object newValue, @Nullable Class<T> requiredType) throws IllegalArgumentException {
return convertIfNecessary(propertyName, oldValue, newValue, requiredType, TypeDescriptor.valueOf(requiredType));
}
以上的流程比较复杂
大致如下
- 首先查找自定义的属性编辑器
findCustomEditor
,此处为null
this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName)
- 使用自定义的ConversionService,此处为null
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
- 查找默认的属性编辑器
editor = findDefaultEditor(requiredType);
在这个查找默认的属性编辑器的过程中,也会按照JDK的规则查找findEditorByConvention
,源码如下
@Nullable
private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
PropertyEditor editor = null;
if (requiredType != null) {
// No custom editor -> check BeanWrapperImpl's default editors.
editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
if (editor == null && String.class != requiredType) {
// No BeanWrapper default editor -> check standard JavaBean editor.
// 此处就是按照JDK的规则查找
editor = BeanUtils.findEditorByConvention(requiredType);
}
}
return editor;
}
- 按照Object、Array、Collection等目标类型依次判断。此处值为String,而目标类型为
com.example.validation.ExoticType
,会尝试获取构造器,根据这个String类型的值构造对应的对象。
此处就返回了,如果目标类型是枚举以及数字,也有类似的逻辑。
如果用户需要自定义属性编辑器该怎么办呢?首先可以按照JDK的方式。定义一个与目标属性类型同包的属性编辑器
package com.example.validation;
import com.example.validation.ExoticType;
import java.beans.PropertyEditorSupport;
public class ExoticTypeEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
如果不想不同类型的类存放一个包中,比如把这个属性编辑器放到com.example.validation.editor
包中,那么就必须进行显示的注册了,通过org.springframework.beans.factory.config.CustomEditorConfigurer
来完成。
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.example.validation.ExoticType" value="com.example.validation.editor.ExoticTypeEditor"/>
</map>
</property>
</bean>
此处就可以在自定义的属性编辑器中查找到
还可以通过另外的方式注册
定义如下类
package com.example.validation;
import com.example.validation.editor.ExoticTypeEditor;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
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...
}
}
在配置文件中进行注册
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar" class="com.example.validation.CustomPropertyEditorRegistrar"/>
在web项目中
package com.example.validation;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.web.bind.ServletRequestDataBinder;
import javax.servlet.http.HttpServletRequest;
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在Spring中还是使用比较广的,只不过主要是内部属性的转换而且Spring中提供了绝大部分默认好的属性编辑器,平时需要自己去定义的场景可能没那么多。另外对于类型转换,另一个类ConversionService
使用起来会更爽。它包括了属性编辑器(PropertyEditor),而且包括了转换器(Converter
),常使用的实现类就是org.springframework.core.convert.support.DefaultConversionService
.使用以下方式就可以获取
ConversionService conversionService = DefaultConversionService.getSharedInstance();
比如
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = new ArrayList<Integer>();
input.add(1);
input.add(2);
List<String> convert = (List<String>) cs.convert(input, TypeDescriptor.forObject(input),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));