什么是属性编辑器?
其实就是来进行类型转换来完成 bean的依赖注入的
来看个 demo:
<bean id="stu" class="ioc.editor.Student">
<property name="birthday" value="2020-05-31" />
</bean>
public class Student {
private Date birthday;
public Date getBirthday() {
return birthday;
}
@Override
public String toString() {
return "Student{" +
"birthday=" + birthday +
'}';
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
Student中 birthday字段为 Date类型,xml配置中为 String类型,你认为正常情况下能够直接注入成功么?
显然不可以
/**
* 验证 propertyEditor
* Cannot convert value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday':
* no matching editors or conversion strategy found
* 可见, 是缺少了属性编辑器的
*/
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc-editor.xml");
Student bean = (Student) context.getBean("stu");
System.out.println(bean);
}
}
既然问题出现在缺乏了 类型转换,因此可以从 propertyEditor下手
常见的有两种解决方式:
- 使用自定义的属性编辑器
- 注册 Spring自带的属性编辑器 CustomDateEditor
这里来演示下第二种解决方案,来继续看个 demo:
<bean id="stu" class="ioc.editor.Student">
<property name="birthday" value="2020-05-31" />
</bean>
<!-- 注册Spring自带的编辑器 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<bean class="ioc.editor.MyDatePropertyEditorRegistrar"></bean>
</list>
</property>
</bean>
</beans>
/**
* 注册 Spring自带的 属性编辑器 CustomDateEditor
*/
public class MyDatePropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
}
运行结果
Student{birthday=Sun May 31 00:00:00 GMT+08:00 2020}
问题是解决了,现在我们来分析下问题是怎么来解决的?深入源码来看一下 registerCustomEditors中到底做了什么?
/**
* 实现了批量注册的功能
*/
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
/** 这里来注册了一系列常用的属性编辑器, 当某 bean中存在对应类型的 字段时, 在依赖注入时就会由 对应的属性编辑器来记性代理注入操作 (其实就是来转换类型) **/
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));
}
}
这里实际上就是来注册了一系列常用的属性编辑器,一旦 bean中存在某些字段类型与注册的类型相对应时, 那么在该字段的注入时会去调用对应的 属性编辑器来进行代理操作
虽然这个方法提供了一系列批量注册的功能,那么实际上在什么时候会进行 此方法的调用?
查看调用链可知:AbstractBeanFactory中的 initWarpper()时会去调用
protected void initBeanWrapper(BeanWrapper bw) {
bw.setConversionService(getConversionService() /** 类型转换器 **/);
/** 来注册一些属性编辑器 **/
registerCustomEditors(bw);
}
这下就能讲通了!
beanWrapper是什么? bean的封装类,将 bean封装起来,以便后续进行一些相关的操作
在 bean进行依赖注入前,就已将 bean封装成了 beanWrapper,此时会去调用 initBeanWrapper方法,此时便会去调用 registerCustomEditors
在 bean的依赖注入前,就会去调用 ResourceEditorRegistrar的 registerCustomEditors方法进行批量的通用的属性编辑器的注册
注册后,在 bean的属性填充环节,便可以让 Spring去使用这些属性编辑器来进行属性的解析操作了
这里再提一点:BeanWrapper 本身就是 属性编辑器注册中心
(实现了 PropertyEditorRegistry), 属性编辑器作用于 beanWrapper内部管理的真实 bean 注入字段值时
当某个字段对应的类型 在 beanWrapper中有对应的 属性编辑器, 那么对于类型的字段值 就会由该 属性编辑器 代理写入