在spring中使用自定义的PropertyEditor

    在文章《spring源码分析-PropertyEditor》中,已经介绍了什么是PropertyEditor以及它用来干嘛。其实,简单地说,它就是用于特定类型与字符串之间的转换的,只不过在不同的场景有不同的作用,例如:

    (1)在从spring容器获取对象时,PropertyEditor对xml中bean定义的value值进行了类型转换,并通过内省的方式设置到对象中。

    (2)spring mvc获取request的参数,当我们需要把param绑定到controller的对象参数时,需要使用ConversionService和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。。。

转载于:https://my.oschina.net/thinwonton/blog/1492107

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值