探秘Spring的PropertyEditor
今天无意之中一位网友咨询一个问题让我有了深入了解一下Spring的PropertyEditor机制,虽然之前也大概知道些,但是都是知道它是什么,却不知道在Spring整个机制中它是如何执行的。今天就趁着中午闲暇时间看了一下Spring这方面的源码。所以这里主要是通过分析Spring的源码来了解PropertyEditor
。
其实PropertyEditor
是JDK里面的java.beans下面的类,是提供AWT进行渲染用的。Spring通过利用该接口,来实现Bean的属性转换器。我们在Spring的xml或者其他途径配置的bean属性都是字符串类型的值,但是对应到每个具体的属性是各种类型的,Spring通过各种PropertyEditor
来对各个属性进行类型转换,在Spring中的PropertyEditor
并不是直接实现PropertyEditor
接口,是通过PropertyEditorSupport
类屏蔽了一些Spring不需要的方法比如paintValue
,从而对它们提供了默认的实现,所以Spring里面的PropertyEditor
都是在PropertyEditorSupport
的基础上实现的。那么先看看Spring到底提供了哪些PropertyEditor
的实现,下面截取了通过IDE工具找到的实现类截图:
这里面基本涉及到了所有JDK提供的类型,知道spring提供哪些PropertyEditor
之后,还得需要这些PropertyEditor
是怎么嵌入Spring的。
PropertyEditorRegistry
这个是Spring框架内部将PropertyEditor
嵌入Spring的方式,其中BeanWrapperImpl
就是一个例子。先看看BeanWrapperImpl
的类图:
可以看到BeanWrapperImpl
其实就是PropertyEditorRegistry
的子类。其实PropertyEditorRegistry
是一个接口,做具体事情的是PropertyEditorRegistrySupport
。在PropertyEditorRegistrySupport
中存在一个方法createDefaultEditors
,这个方法就是初始化Spring中默认PropertyEditor
。下面看看哪些是Spring默认的PropertyEditor
:
private void createDefaultEditors() {
this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);
// Simple editors, without parameterization capabilities.
// The JDK does not contain a default editor for any of these target types.
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
// Default instances of collection editors.
// Can be overridden by registering custom instances of those as custom editors.
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
// Default editors for primitive arrays.
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
// The JDK does not contain a default editor for char!
this.defaultEditors.put(char.class, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
// Spring‘s CustomBooleanEditor accepts more flag values than the JDK‘s default editor.
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
// The JDK does not contain default editors for number wrapper types!
// Override JDK primitive number editors with our own CustomNumberEditor.
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
// Only register config value editors if explicitly requested.
if (this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}
执行完这个那么BeanWrapperImpl
就具备上面类型的转换功能,可能上面能够转换的类型还不能满足我们的需求,那么可以通过另一种方式将PropertyEditor注入到Spring中。
PropertyEditorRegistrar
该接口只有一个方法void registerCustomEditors(PropertyEditorRegistry registry)
,实现该方法就可以往传入的registry添加自定义的PropertyEditor
,一般情况下传入的registry
是BeanWrapperImpl
的实体,所以就等于将自定义的PropertyEditor
注入到BeanWrapperImpl
里面。现在的问题是怎么将PropertyEditorRegistrar
注入到Spring中,那么就可以将自定的PropertyEditor
添加到Spring中。Spring提供了一个类专门用来将第三方的PropertyEditor
注入到它里面,这个类就是CustomEditorConfigurer
,先看看这个类的类图:
发现它是实现了BeanFactoryPostProcessor
接口,那么将会在构造完BeanDefinition
之后调用方法postProcessBeanFactory
,只有整个方法是嵌入Spring的唯一途经,那么看看这个方法里面做了什么事情:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
}
}
if (this.customEditors != null) {
for (Map.Entry<String, ?> entry : this.customEditors.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
Class requiredType = null;
try {
requiredType = ClassUtils.forName(key, this.beanClassLoader);
if (value instanceof PropertyEditor) {
if (logger.isWarnEnabled()) {
logger.warn("Passing PropertyEditor instances into CustomEditorConfigurer is deprecated: " +
"use PropertyEditorRegistrars or PropertyEditor class names instead. " +
"Offending key [" + key + "; offending editor instance: " + value);
}
beanFactory.addPropertyEditorRegistrar(
new SharedPropertyEditorRegistrar(requiredType, (PropertyEditor) value));
}
else if (value instanceof Class) {
beanFactory.registerCustomEditor(requiredType, (Class) value);
}
else if (value instanceof String) {
Class editorClass = ClassUtils.forName((String) value, this.beanClassLoader);
Assert.isAssignable(PropertyEditor.class, editorClass);
beanFactory.registerCustomEditor(requiredType, editorClass);
}
else {
throw new IllegalArgumentException("Mapped value [" + value + "] for custom editor key [" +
key + "] is not of required type [" + PropertyEditor.class.getName() +
"] or a corresponding Class or String value indicating a PropertyEditor implementation");
}
}
catch (ClassNotFoundException ex) {
if (this.ignoreUnresolvableEditors) {
logger.info("Skipping editor [" + value + "] for required type [" + key + "]: " +
(requiredType != null ? "editor" : "required type") + " class not found.");
}
else {
throw new FatalBeanException(
(requiredType != null ? "Editor" : "Required type") + " class not found", ex);
}
}
}
}
}
发现它都调用了beanFactory
的方法将配置的PropertyEditorRegistrar
和customEditors
注入到Spring的bean工厂里面。我们只要往spring容器中注入一个CustomEditorConfigurer
Bean,然后设置propertyEditorRegistrars
和customEditors
属性就可以将自定义的PropertyEditor
注入到Spring中了。当然你也可以不用CustomEditorConfigurer
类,自定义一个类实现BeanFactoryPostProcessor
接口,然后按照CustomEditorConfigurer
类似的路径,也可以达到目的。下面给出一个自定义PropertyEditor
大致流程:
public class CustomPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
super.setAsText(text);
}
@Override
public Object getValue() {
return super.getValue();
}
}
上面是定义了一个自定义PropertyEditor
,下面需要将这个PropertyEditor
注入到Spring里面中:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.xx.foo.FooPojo" value="com.xx.foo.CustomPropertyEditor"/>
</map>
</property>
</bean>
Spring都会调用setAsText
方法将该属性的字面值设置进来,然后需要根据你规定的规范转换成对应的属性对象。
下面再给出一个ClassEditor
的实现,大家可以仿照这个来实现:
public class ClassEditor extends PropertyEditorSupport {
private final ClassLoader classLoader;
/**
* Create a default ClassEditor, using the thread context ClassLoader.
*/
public ClassEditor() {
this(null);
}
/**
* Create a default ClassEditor, using the given ClassLoader.
* @param classLoader the ClassLoader to use
* (or {@code null} for the thread context ClassLoader)
*/
public ClassEditor(ClassLoader classLoader) {
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text)) {
setValue(ClassUtils.resolveClassName(text.trim(), this.classLoader));
}
else {
setValue(null);
}
}
@Override
public String getAsText() {
Class clazz = (Class) getValue();
if (clazz != null) {
return ClassUtils.getQualifiedName(clazz);
}
else {
return "";
}
}
}