一文详述PropertyEditor


前言
首次接触到```java.beans.PropertyEditor```这个类可能是在Spring中遇到,主要的场景如下:
  1. 在xml中定义bean时设置类的属性时传入的是一个字符串,在最后生成类对象的时候是需要转换为对应类型的。

  2. 在web程序中,通过网络传递的数据也需要通过这类编辑器进行转换

下面会首先谈谈JDK中这个类的使用,然后再分析在Spring中使用的姿势。


JDK中的PropertyEditor

其实这个类是属于jdk中的。在jdk中通过java.beans.PropertyEditorManager来对这些属性编辑器进行注册和查找。
比如如下代码

PropertyEditor propertyEditor = PropertyEditorManager.findEditor(int.class);
propertyEditor.setAsText("15");
Object editorValue = propertyEditor.getValue();
Class<?> aClass = editorValue.getClass();

在这里插入图片描述
获取一个int类型的属性编辑器,然后将字符串15转换为Integer的对象(自动装箱操作)。
以上操作之所以能完成就是因为在jdk的包com.sun.beans.editors中已经定义好了部分简单java类型的属性编辑器。
在这里插入图片描述
然后在实例化com.sun.beans.finder.PropertyEditorFinder的时候进行了注册。
在这里插入图片描述
通过PropertyEditorManager根据类型查找指定的属性编辑器的时候依次会使用到ThreadGroupContextPropertyEditorFinder两个类。最终查找其实就是靠PropertyEditorFinder这个类来完成的。

除了以上默认注册的属性编辑器,其实PropertyEditorFinder还支持另外两种方式:

  1. 与属性类型在同一个包,以Editor结尾的类,比如查找foo.bah.Foz时如果没有找到已经注册的,就会查到类foo.bah.FozEditor
    假设存在以下类
package com.example.validation;

public class ExoticType {
    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}

执行以下代码

PropertyEditor propertyEditor = PropertyEditorManager.findEditor(ExoticType.class);
propertyEditor.setAsText("xiao.ming");
Object editorValue = propertyEditor.getValue();
Class<?> aClass = editorValue.getClass();

在这里插入图片描述
此时就无法查找到指定的属性编辑器了。
在该类包内定义如下类(重点:同一个包)

package com.example.validation;

import java.beans.PropertyEditorSupport;

public class ExoticTypeEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

此时结果如下
在这里插入图片描述
2. 如果以上方式还查找不到可以在指定的standardEditorsPackage包中查找名称为FozEditor类。这个standardEditorsPackage默认情况是sun.beans.editors(在PropertyEditorFinder实例化的时候设置的)。当然可以通过java.beans.PropertyEditorManager#setEditorSearchPath来设置。通过java.beans.PropertyEditorManager#getEditorSearchPath来访问当前配置。
在这里插入图片描述
如果将上面自定义的属性编辑器移到另一个包,比如com.example.validation.editor.ExoticTypeEditor
此时执行上面代码仍然找不到。我们可以通过setEditorSearchPath来保证可用。

String[] defSearchPath = PropertyEditorManager.getEditorSearchPath();
PropertyEditorManager.setEditorSearchPath(new String[]{"com.example.validation.editor"});
String[] newSearchPath = PropertyEditorManager.getEditorSearchPath();

在这里插入图片描述
结果显示现在又可以了。以上就是JDK中默认的PropertyEditor查找的三种方式

  1. 已经注册的编辑器
  2. 类名+Editor所代表的类
  3. 查找路径+简单类型+Editor所代表的类
    对源码感兴趣可以参考源码:com.sun.beans.finder.PropertyEditorFinder#find和com.sun.beans.finder.InstanceFinder#find
Spring中的PropertyEditor

Spring中仍然使用JDK中相同的类,但是并没有全部按照JDK的一套。参考org.springframework.beans.BeanUtils#findEditorByConvention这个方法。从方法名可以知道,按照传统方式来查找属性编辑器,所谓的传统方式就是JDK的方式(PropertyEditorManager).
在这里插入图片描述
从以上注释,可以了解到仍然支持PropertyEditorManager中查找同一个包内属性编辑器的规则,但是不使用默认注册的那些primitive types所对应的编辑器了。核心源码如下

String editorName = targetType.getName() + "Editor";
try {
	Class<?> editorClass = cl.loadClass(editorName);
	if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
		if (logger.isInfoEnabled()) {
			logger.info("Editor class [" + editorName +
					"] does not implement [java.beans.PropertyEditor] interface");
		}
		unknownEditorTypes.add(targetType);
		return null;
	}
	return (PropertyEditor) instantiateClass(editorClass);
}

当然Spring不仅仅如此,Spring中执行与PropertyEditorManager相同工作的是org.springframework.beans.PropertyEditorRegistry。并以此建立了更庞大的体系。不仅仅是属性编辑器的注册与获取、属性转换、还包括了属性访问PropertyAccessor、数据的绑定DataBinder等等。
在这里插入图片描述
处于C位的是org.springframework.beans.PropertyEditorRegistrySupport这个类。管理着默认的属性编辑器和自定义属性编辑器。在方法中org.springframework.beans.PropertyEditorRegistrySupport#createDefaultEditors中注册了绝大部分常见类型的属性编辑器。
在这里插入图片描述
其中就包含了基本类型的,这也是为什么要放弃JDK中默认的属性编辑器的原因,因为Spring进行了扩展。
在这里插入图片描述
Spring中使用属性编辑器的一大场景就是在xml中定义的值转换为指定类型的属性时需要使用
假如有以下两个类

package com.example.validation;

public class ExoticType {
    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}
package com.example.validation;

public class DependsOnExoticType {
    private ExoticType type;

    public ExoticType getType() {
        return type;
    }

    public void setType(ExoticType type) {
        this.type = type;
    }
}

比如定义如下bean定义

<bean id="sample" class="com.example.validation.DependsOnExoticType">
    <property name="type" value="com.example.validation.Person"/>
</bean>

编写以下代码

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
DependsOnExoticType sample = (DependsOnExoticType) applicationContext.getBean("sample");

在这里插入图片描述
从以上执行结果不难看出将字符串转换为ExoticType实例设置到了DependsOnExoticType实例当中。这里是怎么做到的呢?在创建一个bean的过程中,会进行属性的填充,并转换为对应的类型。对应源码为
org.springframework.beans.TypeConverterDelegate#convertIfNecessary

public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue,
		Object newValue, @Nullable Class<T> requiredType) throws IllegalArgumentException {
	return convertIfNecessary(propertyName, oldValue, newValue, requiredType, TypeDescriptor.valueOf(requiredType));
}

在这里插入图片描述
以上的流程比较复杂
大致如下

  1. 首先查找自定义的属性编辑器findCustomEditor,此处为null
this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName)
  1. 使用自定义的ConversionService,此处为null
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
  1. 查找默认的属性编辑器
editor = findDefaultEditor(requiredType);

在这个查找默认的属性编辑器的过程中,也会按照JDK的规则查找findEditorByConvention,源码如下

@Nullable
private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
	PropertyEditor editor = null;
	if (requiredType != null) {
		// No custom editor -> check BeanWrapperImpl's default editors.
		editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
		if (editor == null && String.class != requiredType) {
			// No BeanWrapper default editor -> check standard JavaBean editor.
			// 此处就是按照JDK的规则查找
			editor = BeanUtils.findEditorByConvention(requiredType);
		}
	}
	return editor;
}
  1. 按照Object、Array、Collection等目标类型依次判断。此处值为String,而目标类型为com.example.validation.ExoticType,会尝试获取构造器,根据这个String类型的值构造对应的对象。
    在这里插入图片描述
    此处就返回了,如果目标类型是枚举以及数字,也有类似的逻辑。

如果用户需要自定义属性编辑器该怎么办呢?首先可以按照JDK的方式。定义一个与目标属性类型同包的属性编辑器

package com.example.validation;

import com.example.validation.ExoticType;

import java.beans.PropertyEditorSupport;

public class ExoticTypeEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

在这里插入图片描述
如果不想不同类型的类存放一个包中,比如把这个属性编辑器放到com.example.validation.editor包中,那么就必须进行显示的注册了,通过org.springframework.beans.factory.config.CustomEditorConfigurer来完成。

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="com.example.validation.ExoticType" value="com.example.validation.editor.ExoticTypeEditor"/>
        </map>
    </property>
</bean>

此处就可以在自定义的属性编辑器中查找到
在这里插入图片描述

还可以通过另外的方式注册
定义如下类

package com.example.validation;

import com.example.validation.editor.ExoticTypeEditor;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
    }
}

在配置文件中进行注册

    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="propertyEditorRegistrars">
            <list>
                <ref bean="customPropertyEditorRegistrar"/>
            </list>
        </property>
    </bean>
    <bean id="customPropertyEditorRegistrar" class="com.example.validation.CustomPropertyEditorRegistrar"/>

在web项目中

package com.example.validation;

import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.web.bind.ServletRequestDataBinder;

import javax.servlet.http.HttpServletRequest;

public final class RegisterUserController extends SimpleFormController {
    
	private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
                              ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }
    
    // other methods to do with registering a User
}
总结

属性编辑器PropertyEditor在Spring中还是使用比较广的,只不过主要是内部属性的转换而且Spring中提供了绝大部分默认好的属性编辑器,平时需要自己去定义的场景可能没那么多。另外对于类型转换,另一个类ConversionService使用起来会更爽。它包括了属性编辑器(PropertyEditor),而且包括了转换器(Converter),常使用的实现类就是org.springframework.core.convert.support.DefaultConversionService.使用以下方式就可以获取

ConversionService conversionService = DefaultConversionService.getSharedInstance();

比如

DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = new ArrayList<Integer>();
input.add(1);
input.add(2);
List<String> convert = (List<String>) cs.convert(input, TypeDescriptor.forObject(input),
        TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值