在文章《spring源码分析-PropertyEditor》中,已经介绍了什么是PropertyEditor以及它用来干嘛。其实,简单地说,它就是用于特定类型与字符串之间的转换的,只不过在不同的场景有不同的作用,例如:
(1)在从spring容器获取对象时,PropertyEditor对xml中bean定义的value值进行了类型转换,并通过内省的方式设置到对象中。
(2)spring mvc获取request的参数,当我们需要把param绑定到controller的对象参数时,需要使用PropertyEditor对字符串进行类型转换,设置到绑定对象。
在这里,不再赘述它的一些概念和原理,如果不清楚它的概念的话,还是建议先看一下《spring源码分析-PropertyEditor》,因为本文将会作为该文章的补充。
在本文我们将探讨如何在spring中使用自定义的PropertyEditor,然后比较自定义PropertyEditor在spring不同版本中的注册方式,除此之外,我们还将简单剖析一下CustomEditorConfigurer的源码。
需求与实现
先了解一下本文的需求。我们需要通过spring为order实例注入createDate。order类和spring 中的bean配置如下:
public class Order { private Date createDate; public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } @Override public String toString() { return "Order{" + "createDate=" + createDate + '}'; } }
<bean id="order" class="com.github.thinwonton.spring.source.analysis.propertyeditor.Order"> <property name="createDate" value="2016-07-09 23:44:18"/> </bean>
显然,这样是不能注入成功的,并且会抛出异常,这是因为spring默认情况下没有注册解决java.util.Date的属性编辑器,既然没有注册,那么接下来就抓紧注册一个属性编辑器解决类型转换问题吧。
定义一个解决Date类型的属性编辑器
package com.github.thinwonton.spring.source.analysis.propertyeditor; import org.springframework.format.datetime.DateFormatter; import org.springframework.util.StringUtils; import java.beans.PropertyEditorSupport; import java.text.ParseException; import java.util.Date; import java.util.Locale; public class CustomDateEditor extends PropertyEditorSupport { /** * Date的转换模板 */ private final String pattern; private final boolean allowEmpty; private final DateFormatter dateFormatter; public CustomDateEditor(String pattern) { this(pattern, true); } public CustomDateEditor(String pattern, boolean allowEmpty) { this.pattern = pattern; this.allowEmpty = allowEmpty; this.dateFormatter = new DateFormatter(this.pattern); } @Override public void setAsText(String text) throws IllegalArgumentException { if (!StringUtils.hasText(text) && this.allowEmpty) { // 字符为""或者null,并且属性编辑器允许属性为null,把属性设为null setValue(null); } else if (!StringUtils.hasText(text)) { // 字符为""或者null,并且属性编辑器不允许属性为null,抛异常 throw new IllegalArgumentException( "date string is empty, but allowEmpty argument is false"); } else { try { setValue(this.dateFormatter.parse(text, Locale.getDefault())); } catch (ParseException ex) { throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex); } } } @Override public String getAsText() { Date value = (Date) getValue(); return (value != null ? this.dateFormatter.print(value, Locale.getDefault()) : ""); } }
Date的属性编辑器将从xml的value属性中读取字符串,根据配置好的模板转换成Date对象,设置值。
定义好属性编辑器后,我们需要把它注册到spring,由它进行管理。
注册自定义的属性编辑器
自定义的属性编辑器注册,在spring 3.x版本和4.x版本的注册方式有点不同。
我们先看一下spring 3.x版本的注册。
spring 3.x版本的注册方式
<bean id="customDateEditor" class="com.github.thinwonton.spring.source.analysis.propertyeditor.CustomDateEditor"> <constructor-arg name="allowEmpty" value="true"/> <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/> </bean> <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="java.util.Date"> <ref bean="customDateEditor"/> </entry> </map> </property> </bean>
一起看一下spring 3.x版本中CustomEditorConfigurer的customEditors属性
private Map<String, ?> customEditors; public void setCustomEditors(Map<String, ?> customEditors) { this.customEditors = customEditors; }
customEditors是一个map结构的属性,它的value是可以接受任何类型的。那么这些自定义的属性编辑器是在什么时候注册的呢?
CustomEditorConfigurer实现了后置处理器BeanFactoryPostProcessor,容器初始化时会调用它的postProcessBeanFactory方法。直接看一下postProcessBeanFactory方法。
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); } } } } }
这段代码很简单,就是propertyEditorRegistrars 不为空时,把它添加到spring的bean工厂中,其实propertyEditorRegistrars也保存了自定义属性编辑器,它会在之后由spring取出来注册到容器中;后面的代码是遍历customEditors,判断entry的value类型,如果是PropertyEditor类型就使用注册器添加到bean工厂;如果是class类型就直接注册,后面再实例化。
其实,这段代码透露给我们另一种注册属性编辑器的方法,就是通过propertyEditorRegistrars注册。这个也是spring 4.x采用的方法,该方法放在spring 4.x部分介绍。
spring 4.x版本的注册方式
一起翻一下CustomEditorConfigurer的源码吧
private Map<Class<?>, Class<? extends PropertyEditor>> customEditors; public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) { this.customEditors = customEditors; }
customEditors的数据结构发生了变化,尽管它还是map,但它的value变成了class了,也就意味着,我们还是想通过customEditors注入属性编辑器,只能注入class了。注入class后,将由spring负责实例化。有个非常值得关注的问题是,spring实例化bean,在没有指定构造方法的时候,是使用无参构造方法实例化的,回头看一下前面定义的CustomDateEditor,没有无参构造方法。行不通啊。
接下来看一下postProcessBeanFactory方法的源码。
@Override 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<Class<?>, Class<? extends PropertyEditor>> entry : this.customEditors.entrySet()) { Class<?> requiredType = entry.getKey(); Class<? extends PropertyEditor> propertyEditorClass = entry.getValue(); beanFactory.registerCustomEditor(requiredType, propertyEditorClass); } } }
propertyEditorRegistrars 这段代码还存在。也就是说,不管spring 3.x或者4.x,我们都可以通过propertyEditorRegistrars 注入属性编辑器,从而注册我们的属性编辑器。
接下来,将介绍如何通过CustomEditorConfigurer的propertyEditorRegistrars 属性,注入我们的属性编辑器。
实现PropertyEditorRegistrar
package com.github.thinwonton.spring.source.analysis.propertyeditor; import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; import java.beans.PropertyEditor; import java.util.Map; import java.util.Set; public class CustomEditorRegistrar implements PropertyEditorRegistrar { private Map<Class<?>, PropertyEditor> customEditors; public void registerCustomEditors(PropertyEditorRegistry registry) { if (customEditors != null) { Set<Map.Entry<Class<?>, PropertyEditor>> entries = customEditors.entrySet(); for (Map.Entry<Class<?>, PropertyEditor> entry : entries) { registry.registerCustomEditor(entry.getKey(), entry.getValue()); } } } public void setCustomEditors(Map<Class<?>, PropertyEditor> customEditors) { this.customEditors = customEditors; } }
定义了一个customEditors属性,可以在xml中注入属性编辑器。
spring中配置PropertyEditorRegistrar和注入属性编辑器
<bean id="customEditorRegistrar" class="com.github.thinwonton.spring.source.analysis.propertyeditor.CustomEditorRegistrar"> <property name="customEditors"> <map> <entry key="java.util.Date" value-ref="customDateEditor"/> </map> </property> </bean> <bean id="customDateEditor" class="com.github.thinwonton.spring.source.analysis.propertyeditor.CustomDateEditor"> <constructor-arg name="allowEmpty" value="true"/> <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/> </bean> <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <ref bean="customEditorRegistrar"/> </list> </property> </bean>
一切OK。。。