在spring中使用自定义的PropertyEditor


声明:本文转载自https://my.oschina.net/thinwonton/blog/1492107,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

    在文章《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。。。

本文发表于2017年07月30日 08:36
(c)注:本文转载自https://my.oschina.net/thinwonton/blog/1492107,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2240 讨论 1 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1