探秘Spring的PropertyEditor

探秘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,一般情况下传入的registryBeanWrapperImpl的实体,所以就等于将自定义的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的方法将配置的PropertyEditorRegistrarcustomEditors注入到Spring的bean工厂里面。我们只要往spring容器中注入一个CustomEditorConfigurerBean,然后设置propertyEditorRegistrarscustomEditors属性就可以将自定义的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 "";
    }
}

}
时间: 2024-09-15 01:09:18

探秘Spring的PropertyEditor的相关文章

Spring 4.x Bean操作和BeanWrapper

BeanWrapper的工作方式在一定程度上是由它的名字表示:它包装一个Bean在其上执行动作,例如设置和检索属性. 在beans包中的一个很重要的类是BeanWrapper接口和它响应的实现(BeanWrapperImpl).BeanWrapper提供功能(单独或批量)设置和获取属性值,获取属性描述,和查询属性决定它们是否可读或可写.同时,BeanWrapper提供支持嵌套属性,能够设置属性的无限深度的子属性.此外,BeanWrapper支持添加标准JavaBeans的PropertyChan

20200103 Spring官方文档(Core 3)

3.验证,数据绑定和类型转换 考虑将验证作为业务逻辑是有利有弊,Spring提供了一种验证(和数据绑定)设计,但并不排除其中任何一个. 具体来说,验证不应与Web层绑定,并且应该易于本地化,并且应该可以插入任何可用的验证器. 考虑到这些问题,Spring提出了一个Validator接口,该接口既基本又可以在应用程序的每一层中使用. 数据绑定对于使用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象)非常有用. Spring提供了恰当地命名为DataBinder的功能. Validat

属性编辑器,即PropertyEditor--&gt;Spring IoC

在Spring配置文件里,我们往往通过字面值为Bean各种类型的属性提供设置值:不管是double类型还是int类型,在配置文件中都对应字符串类型的字面值.BeanWrapper填充Bean属性时如何将这个字面值转换为对应的double或int等内部类型呢?我们可以隐约地感觉到一定有一个转换器在其中起作用,这个转换器就是属性编辑器. “属性编辑器”这个名字可能会让人误以为是一个带用户界面的输入器,其实属性编辑器不一定非得有用户界面,任何实现java.beans.PropertyEditor接口的

spring自动类型转换========Converter和PropertyEditor

Spring有两种自动类型转换器,一种是Converter,一种是propertyEditor. 两者的区别:Converter是类型转换成类型,Editor:从string类型转换为其他类型. 从某种程度上,Converter包含Editor.如果出现需要从string转换到其他类型.首选Editor. Converter代码展示: 实现string类型转换Date. MyConverter类 public class MyConverter implements Converter<Stri

关于在Spring中注册自定义的PropertyEditor

考虑下面两个类: ExoticType: package document.six.test; public class ExoticType { private String name; public ExoticType(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } Dpen

spring配置自定义的PropertyEditor

在项目开发过程中,总会有这样那样由于格式转换带来的问题.如:money精确度的处理,这个应该在电商中非常常见.日期格式的处理,几乎是所有开发过程遇到的.本文通过介绍java.beans.PropertyEditor 复习一下. PropertyEditor .PropertyEditorSupport 在上层是一个Property的简单接口,仅仅定义了setValue setAsText等几个非常简单的接口.后面通过PropertyEditorSupport来做了一些实现.最常用的方法也就是se

spring探秘:通过BeanPostProcessor、@PostConstruct、InitializingBean在启动前执行方法

springboot启动前执行方法的3种方式:实现BeanPostProcessor接口.实现InitializingBean接口.使用@PostConstruct注解 示例: 第一种 实现BeanPostProcessor接口 @Configuration public class Test3 implements BeanPostProcessor { @Autowired private Environment environment; @Override public Object po

【转】大数据批处理框架 Spring Batch全面解析

如今微服务架构讨论的如火如荼.但在企业架构里除了大量的OLTP交易外,还存在海量的批处理交易.在诸如银行的金融机构中,每天有3-4万笔的批处理作业需要处理.针对OLTP,业界有大量的开源框架.优秀的架构设计给予支撑:但批处理领域的框架确凤毛麟角.是时候和我们一起来了解下批处理的世界哪些优秀的框架和设计了,今天我将以Spring Batch为例,和大家一起探秘批处理的世界.初识批处理典型场景探秘领域模型及关键架构实现作业健壮性与扩展性批处理框架的不足与增强批处理典型业务场景对账是典型的批处理业务处

好记性不如烂笔头83-spring3学习(4)-spring的BeanFactory(IoC容器)

我们一般把BeanFactory叫做IoC容器,叫ApplicationContext 为应用上下文(或者Spring容器) BeanFactory是spring框架的核心,实现依赖注入[使个组件的依赖关系从代码中独立出来,使用配置文件即可实现这种依赖关系]和bean声明周期的管理 . BeanFactory[IoC容器]启动过程:分为两个阶段,一个是容器启动阶段,另外一个是Bean实例化阶段 容器启动阶段:加载配置 -–> 分析配置信息 -–>装备到BeanDefinition -–>