属性编辑器 PropertyEditor

系列文章目录

Spring 5.2.9 source code



前言

参考: CoderLi 老哥的 探秘 Spring 的 PropertyEditor

PropertyEditor 在 Spring 中的作用

XML 配置 Bean 或者 @Value(“xxx”) 或者 properties 文件中的属性赋值到 Bean 中的时候, 属性字段值很多是靠 文本类型的字符串 设置的, 但是属性的类型却可能是 String, Integer, File, Class, Properties, Resource 等类型, PropertyEditor 就是用来把文本型的值转换为对应类型值的工具, 其关键方法是 void setAsText(String text)

演示

新增一个 Bean, 用 @Value 解析 String 类型的 Date

@Component
public class Foo {
    
    
	@Value("2020-10-20 15:18:27")
	private Date date;
	@Override
	public String toString() {
    
    
		return "Foo{date=" + date + '}';
	}
}

启动 Spring 容器

public static void main(String[] args) {
    
    
	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Main.class);
	Foo foo = ac.getBean("foo", Foo.class);
	System.out.println(foo);
}

会报错

java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Date': no matching editors or conversion strategy found

添加一个 String 转 Date 类型的 PropertyEditor, 重写 setAsText 方法, 把传入的 String 类型的 text 转换为 Date, 并调用 setValue

public class MyDateEditor extends PropertyEditorSupport {
    
    
	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	@Override
	public void setAsText(String text) throws IllegalArgumentException {
    
    
		setValue(Date.from(LocalDateTime.parse(text, FORMATTER).atZone(ZoneId.systemDefault()).toInstant()));
	}
}

再配置一个 CustomEditorConfigurer (是一个 BFPP BeanFactoryPostProcessor)

@Bean
public CustomEditorConfigurer propertyResourceConfigurer() {
    
    
	CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
	Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();
	customEditors.put(Date.class, MyDateEditor.class);
	customEditorConfigurer.setCustomEditors(customEditors);
	return customEditorConfigurer;
}

这时可以转化并输出正确的内容

Foo{
    
    date=Tue Oct 20 15:18:27 CST 2020}

PropertyEditor

PropertyEditor 是 JDK 自带的接口, 主要用途就是将用户在图形见面中输入的字符串转换为对应类型的值(对象). 类似于一个 convertor, 但是要注意 PropertyEditor 对象有一个字段 value, 会保存解析后的值, 所以如果在多线程环境下使用同一个 PropertyEditor 对象会有同步问题

主要方法有四个

void setValue(Object value); 设置属性值,
Object getValue(); 获取属性值
String getAsText(); 把属性值转换成 String
void setAsText(String text); 把 String 转换成属性值

基本使用流程是, 调用 setAsText 把传入的 text 解析为对应类型的值, 内部调用 setValue 把该值保存在 PropertyEditor 对象内部, 可通过 getValue 方法把该值取出, 然后调用目标对象的对应 set 方法把值赋值给目标对象的属性

PropertyEditorSupport

JDK 提供的 PropertyEditor 的默认实现类, 各种自定义的 PropertyEditor 都是继承该类来实现的, 只需要重写 setAsText 方法

PropertyEditorRegistry

PropertyEditor 注册表, 即保存 PropertyEditor 的地方, Spring 提供的用于定义注册 PropertyEditor 规范的接口

void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);
@Nullable
PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);

PropertyEditorRegistrySupport

是 PropertyEditorRegistry 的实现类. 当我们尝试去通过 Class 对象获取对应的 PropertyEditor 的时候, 它会为我们初始化一系列默认的 PropertyEditor

在 doCreateBean 的 populateBean 中会调用 getDefaultEditor 获取对应的 PropertyEditor 进行值的类型转换

// Spring 提供的默认 PropertyEditor
private Map<Class<?>, PropertyEditor> defaultEditors;

// 用于覆盖默认 PropertyEditor 的 PropertyEditor
private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;

// 自定义的 PropertyEditor
private Map<Class<?>, PropertyEditor> customEditors;

// 属性的路径/属性名,CustomEditorHolder 包含的是 Class 和对应的 PropertyEditor
private Map<String, CustomEditorHolder> customEditorsForPath;

// 如果注册的是父 Class, 那么子类的 Class 找不到的时候, 就会返回这个父的 Class, 并且将这个关系保存在这个 customEditorCache 中
private Map<Class<?>, PropertyEditor> customEditorCache;
@Nullable
public PropertyEditor getDefaultEditor(Class<?> requiredType) {
    
    
	if (!this.defaultEditorsActive) {
    
    
		return null;
	}
	if (this.overriddenDefaultEditors != null) {
    
    
		PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
		if (editor != null) {
    
    
			return editor;
		}
	}
	if (this.defaultEditors == null) {
    
    
		createDefaultEditors();
	}
	return this.defaultEditors.get(requiredType);
}

执行 createDefaultEditors 方法会初始化 defaultEditors, 并添加一些默认的 PropertyEditor

PropertyEditorRegistrar

PropertyEditor 注册器, 用于把 PropertyEditor 注册到注册表中, 传入一个注册表, 代理其注册方法

ResourceEditorRegistrar

PropertyEditorRegistrar 的唯一默认实现, 只有使用 ApplicationContext 的时候这个 Registrar 才会被使用到, 下面的 PropertyEditor 才会去注册或者覆盖 PropertyEditorRegistry 默认的值

继而它会在 doCreateBean - createBeanInstance - autowireConstructor - initBeanWrapper - registerCustomEditors 中将 ResourceEditorRegistrar 的默认的 PropertyEditor 注册进去

见 refresh - prepareBeanFactory

beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
    
    
	ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
	doRegisterEditor(registry, Resource.class, baseEditor);
	doRegisterEditor(registry, ContextResource.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));
	}
}
private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) {
    
    
	if (registry instanceof PropertyEditorRegistrySupport) {
    
    
		((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor);
	}
	else {
    
    
		registry.registerCustomEditor(requiredType, editor);
	}
}

BeanWrapper

Spring 中用于封装 Bean 的是 BeanWrapper, 而它又间接继承了 PropertyEditorRegistry. BeanWrapperImpl 是 BeanWrapper 的实现类, 我们在系统中看到的大多数 PropertyEditorRegistry 都是 BeanWrapperImpl 的对象. BeanWrapperImpl 还继承了 PropertyEditorRegistrySupport 这个实现类

总结

目前关于 PropertyEditor 及其相关类在 Spring 中的用途有了一定的了解, 但是具体的调用流程还是不太清楚, 待后续补充

在 doCreateBean 的 populateBean 中会调用 getDefaultEditor 获取对应的 PropertyEditor 进行值的类型转换?
在 doCreateBean - createBeanInstance - autowireConstructor - initBeanWrapper - registerCustomEditors 中将 ResourceEditorRegistrar 的默认的 PropertyEditor 注册进去?

猜你喜欢

转载自blog.csdn.net/mrathena/article/details/109115717