java Bean规范中有这样一个接口,PropertyEditor,从这个接口的名字来看,是用来进行编辑属性的,那自然是编辑对象的属性。
1.为什么需要属性编辑器呢
我们通常会在类型定义各种类型的属性,通常我们自己通过new创建对象,并且设置属性的时候,我们知道每个字段是什么类型,我们就会设置一个对应类型的值给指定的字段。但是有的时候我们需要将一个字符串,转换为某个字段的类型,因为有些类型,如果是从字符串转换为该类型,比较复杂,所以我们就需要一个工具类来抽取转换的代码,将字符串转换为指定的类型。所以java.beans中定义了PropertyEditor,该接口中定义的void setAsText(String text) 就是将传入的字符串text转换为指定类型的对象,为了方便使用,java为这个接口定义了一个默认的实现类PropertyEditorSupport类,它帮助我们实现了所有的方法,我们只需要继承它就可以使用了。在PropertyEditorSupport定义了一个value的字段,setAsText()中 将字符串转化为指定类型的对象以后,通过setValue() 把该对象赋值给 value字段,这样我们就可以通过getValue()方法获取到我们想要的对象
2.属性编辑器能完成什么样的功能
(1) 一个PropertyEditor的实现类,可以帮我们完成把一个字符串转换为一个我们想要的对象
(2)该类还定义了一个 String getAsText() ,主要就是返回一个能代表指定类型的对象,并且我们看得懂的字符串
3.在Spring中应用场景
在Spring中我们有大量的这种场景,比如我们在一个类中定义了一个URL类型的字段,但是我们在xml文件中通过Property标签配置该字段是只能配置字符串类型,所以就需要一个PropertyEditor 将我们配置的 字符串转换为 URL类型的对象。比如在SpringMvc中,我们定义的接收参数的Model中可能有各种各样类型的字段,有时候还可能定义一个数组类型的字段,但是前端传过来的参数都是字符串,所以就需要有对应的PropertyEditor将字符串转换为想要的类型的对象
我们自定义一个PropertyEditor来测试一下
public class PersonEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] split = text.split("#");
Person person = new Person();
person.setName(split[0]);
person.setAge(Integer.parseInt(split[1]));
this.setValue(person);
}
@Override
public String getAsText() {
return ((Person)this.getValue()).getName() + "#" + ((Person)this.getValue()).getAge();
}
public static void main(String[] args) {
PersonEditor personEditor = new PersonEditor();
personEditor.setAsText("孙悟空#30");
System.out.println(personEditor.getValue());
System.out.println(personEditor.getAsText());
}
}
运行结果:
Person{name='孙悟空', age=30}
孙悟空#30
可以看到我们成功将字符串 孙悟空#30 转换为一个Person对象,这样如果我们在接收参数的Model中定义了Person类型的字段,前端传了 孙悟空#30 ,我们就可以用PersonEditor来将
孙悟空#30 转换为一个Person对象
4.接下来介绍一个Spring自己定义好的PropertyEditor,ResourceEditor
Editor for Resource descriptors, to automatically convert String locations e.g. file:C:/myfile.txt or classpath:myfile.txt to Resource properties instead of using a String location property.
The path may contain ${...} placeholders, to be resolved as org.springframework.core.env.Environment properties: e.g. ${user.dir}. Unresolvable placeholders are ignored by default.
资源描述符的编辑器,自动将字符串,例如:file:C:/myfile.txt 或者 classpath:myfile.txt 转换为Resource类型的属性,而不是使用字符串类型的属性。
以上这句话的意思就是,如果一个类的属性是 Resource 类型的,但是我们知道资源的字符串路径,这时候就需要通过ResourceEditor将字符串路径转换为Resource类型的对象,赋值给Resource 类型的属性。
字符串路径可能包含 ${...} 占位符,这个占位符将解析为环境中的变量,例如:${user.dir}就会被环境中的 user.dir 替换掉,解析不了的占位符默认会被忽略
public class ResourceEditor extends PropertyEditorSupport {
private final ResourceLoader resourceLoader;
@Nullable
private PropertyResolver propertyResolver;
private final boolean ignoreUnresolvablePlaceholders;
/**
* Create a new instance of the {@link ResourceEditor} class
* using a {@link DefaultResourceLoader} and {@link StandardEnvironment}.
*/
public ResourceEditor() {
this(new DefaultResourceLoader(), null);
}
/**
* Create a new instance of the {@link ResourceEditor} class
* using the given {@link ResourceLoader} and {@link PropertyResolver}.
* @param resourceLoader the {@code ResourceLoader} to use
* @param propertyResolver the {@code PropertyResolver} to use
*/
public ResourceEditor(ResourceLoader resourceLoader, @Nullable PropertyResolver propertyResolver) {
this(resourceLoader, propertyResolver, true);
}
/**
* Create a new instance of the {@link ResourceEditor} class
* using the given {@link ResourceLoader}.
* @param resourceLoader the {@code ResourceLoader} to use
* @param propertyResolver the {@code PropertyResolver} to use
* @param ignoreUnresolvablePlaceholders whether to ignore unresolvable placeholders
* if no corresponding property could be found in the given {@code propertyResolver}
*/
public ResourceEditor(ResourceLoader resourceLoader, @Nullable PropertyResolver propertyResolver,
boolean ignoreUnresolvablePlaceholders) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
// 根据上面的说明 要把字符串转化为一个Resource对象,首先需要一个资源加载器按照字符串路
// 径加载资源
this.resourceLoader = resourceLoader;
// 字符串路径中包含占位符,所以需要PropertyResolver来解析占位符
this.propertyResolver = propertyResolver;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
@Override
public void setAsText(String text) {
if (StringUtils.hasText(text)) {
// 这里的转换过程很简单,就是先解析路径中的占位符,得到完整的路径
// 然后通过资源加载器将路径加载为一个Resource对象,通过setValue()
// 方法将Resource对象赋值给 value 字段,这样其他组件就可以通过
// getValue() 获取到该Resource对象
String locationToUse = resolvePath(text).trim();
setValue(this.resourceLoader.getResource(locationToUse));
}
else {
setValue(null);
}
}
/**
* Resolve the given path, replacing placeholders with corresponding
* property values from the {@code environment} if necessary.
* @param path the original file path
* @return the resolved file path
* @see PropertyResolver#resolvePlaceholders
* @see PropertyResolver#resolveRequiredPlaceholders
*/
protected String resolvePath(String path) {
if (this.propertyResolver == null) {
// 如果构造方法中没有指定propertyResolver,默认使用
// StandardEnvironment,StandardEnvironment实现了
// PropertyResolver
this.propertyResolver = new StandardEnvironment();
}
return (this.ignoreUnresolvablePlaceholders ? this.propertyResolver.resolvePlaceholders(path) :
this.propertyResolver.resolveRequiredPlaceholders(path));
}
@Override
@Nullable
public String getAsText() {
Resource value = (Resource) getValue();
try {
// Try to determine URL for resource.
return (value != null ? value.getURL().toExternalForm() : "");
}
catch (IOException ex) {
// Couldn't determine resource URL - return null to indicate
// that there is no appropriate text representation.
return null;
}
}
}
5.Spring 中常用的PropertyEditor
/**
* PropertyEditorRegistrar implementation that populates a given
* {@link org.springframework.beans.PropertyEditorRegistry}
* (typically a {@link org.springframework.beans.BeanWrapper} used for bean
* creation within an {@link org.springframework.context.ApplicationContext})
* with resource editors. Used by
* {@link org.springframework.context.support.AbstractApplicationContext}.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 2.0
*/
public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
private final PropertyResolver propertyResolver;
private final ResourceLoader resourceLoader;
/**
* Create a new ResourceEditorRegistrar for the given {@link ResourceLoader}
* and {@link PropertyResolver}.
* @param resourceLoader the ResourceLoader (or ResourcePatternResolver)
* to create editors for (usually an ApplicationContext)
* @param propertyResolver the PropertyResolver (usually an Environment)
* @see org.springframework.core.env.Environment
* @see org.springframework.core.io.support.ResourcePatternResolver
* @see org.springframework.context.ApplicationContext
*/
public ResourceEditorRegistrar(ResourceLoader resourceLoader, PropertyResolver propertyResolver) {
this.resourceLoader = resourceLoader;
this.propertyResolver = propertyResolver;
}
/**
* Populate the given {@code registry} with the following resource editors:
* ResourceEditor, InputStreamEditor, InputSourceEditor, FileEditor, URLEditor,
* URIEditor, ClassEditor, ClassArrayEditor.
* <p>If this registrar has been configured with a {@link ResourcePatternResolver},
* a ResourceArrayPropertyEditor will be registered as well.
* @see org.springframework.core.io.ResourceEditor
* @see org.springframework.beans.propertyeditors.InputStreamEditor
* @see org.springframework.beans.propertyeditors.InputSourceEditor
* @see org.springframework.beans.propertyeditors.FileEditor
* @see org.springframework.beans.propertyeditors.URLEditor
* @see org.springframework.beans.propertyeditors.URIEditor
* @see org.springframework.beans.propertyeditors.ClassEditor
* @see org.springframework.beans.propertyeditors.ClassArrayEditor
* @see org.springframework.core.io.support.ResourceArrayPropertyEditor
*/
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 容器启动时,会给beanFactory的添加一个ResourceEditorRegistrar对象
// beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this,
// getEnvironment()));
// 当调用这个ResourceEditorRegistrar对象的registerCustomEditors方法时,
// 会将下面所有的PropertyEditor注册到传过来的PropertyEditorRegistry对象
ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
doRegisterEditor(registry, Resource.class, baseEditor);
doRegisterEditor(registry, ContextResource.class, baseEditor);
doRegisterEditor(registry, WritableResource.class, baseEditor);
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
doRegisterEditor(registry, Path.class, new PathEditor(baseEditor));
doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));
doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));
ClassLoader classLoader = this.resourceLoader.getClassLoader();
doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));
if (this.resourceLoader instanceof ResourcePatternResolver) {
doRegisterEditor(registry, Resource[].class,
new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
}
}
/**
* Override default editor, if possible (since that's what we really mean to do here);
* otherwise register as a custom editor.
*/
private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) {
if (registry instanceof PropertyEditorRegistrySupport) {
((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor);
}
else {
registry.registerCustomEditor(requiredType, editor);
}
}
}
org.springframework.beans.factory.support.AbstractBeanFactory#getTypeConverter()
@Override
public TypeConverter getTypeConverter() {
TypeConverter customConverter = getCustomTypeConverter();
if (customConverter != null) {
return customConverter;
}
else {
// Build default TypeConverter, registering custom editors.
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
typeConverter.setConversionService(getConversionService());
//SimpleTypeConverter 其实也实现了 PropertyEditorRegistry
registerCustomEditors(typeConverter);
return typeConverter;
}
}
org.springframework.beans.factory.support.AbstractBeanFactory#registerCustomEditors(PropertyEditorRegistry registry)
/**
* Initialize the given PropertyEditorRegistry with the custom editors
* that have been registered with this BeanFactory.
* <p>To be called for BeanWrappers that will create and populate bean
* instances, and for SimpleTypeConverter used for constructor argument
* and factory method type conversion.
* @param registry the PropertyEditorRegistry to initialize
*/
protected void registerCustomEditors(PropertyEditorRegistry registry) {
if (registry instanceof PropertyEditorRegistrySupport) {
((PropertyEditorRegistrySupport) registry).useConfigValueEditors();
}
// 在启动容器的时候,org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
// 会给beanFactory中添加 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// 所以默认propertyEditorRegistrars 中会有一个ResourceEditorRegistrar对象
// 当org.springframework.beans.factory.support.AbstractBeanFactory#getTypeConverter() 中调用本方法时,就会把
// ResourceEditorRegistrar.registerCustomEditors(PropertyEditorRegistry registry) 方法中写死的那些 PropertyEditor
// 全都注册到新创建的 SimpleTypeConverter typeConverter = new SimpleTypeConverter();
// SimpleTypeConverter 其实也实现了 PropertyEditorRegistry
if (!this.propertyEditorRegistrars.isEmpty()) {
for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
try {
registrar.registerCustomEditors(registry);
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
"] failed because it tried to obtain currently created bean '" +
ex.getBeanName() + "': " + ex.getMessage());
}
onSuppressedException(ex);
continue;
}
}
throw ex;
}
}
}
if (!this.customEditors.isEmpty()) {
this.customEditors.forEach((requiredType, editorClass) ->
registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
}
}