spring的属性编辑器/属性转换器
2020-03-16 17:38:17 0 举报
从应用场景到使用实现加上源码带你一次性搞定spring的属性编辑器和转换器的实现机制
作者其他创作
大纲/内容
下面让我们进入委托类的convertIfNecessary方法,下面截图只是一部分,但是就是本文要讲的重点。如图逻辑:第一先尝试获取自定义编辑器,如果有自定义编辑器,则直接使用自定义编辑器进行属性转换;如果没有自定义编辑器,则下一步尝试获取自定义的转换器服务conversionService,如果有,判断可以进行属性转换(是否有相应类型的转换器),且转换过程没有异常的话,则转换结束,直接返回结果;如果没有自定义编辑器,且也没有conversionService或者conversionService中没有匹配到相应转换器,或者匹配到转换器其实最终转换失败,则此时选择从默认的编辑器中去匹配。这便是属性编辑器和属性转换器的应用逻辑。正常情况下,自定义编辑器(customEditor)和转换器服务(conversionService)都是没有设置的,因此都是从默认的编辑器中去匹配,当类似于String转Date这种无法从默认的编辑器中获取时,则需要我们使用自定义的方式进行扩展。也就是上面的自定义编辑器/自定义转换器,甚至可以自定义一个重载的默认编辑器。(这个默认编辑器其实还有两层,1.BeanFactory中自己注册了最底层默认的编辑器,2.而ApplicactionContext的扩展中通过ResourceEditorRegistrar重构了一份,具体后面会讲解。)
应用的方式讲完了,现在我们来看一下spring到底是怎么实现通过CustomEditorConfigurer将编辑器注册到注册器中的。CustomEditorConfigurer类实现了BeanFactoryPostProcessor接口,关于BeanFactoryPostProcessor的内容,就不在本文中详细介绍了。BeanFactoryPostProcessor是一个工厂级的后处理器,ApplicationContext在对BeanFactory的扩展中,对实现该接口的所有类进行了初始化操作,当我们使用ApplicationContext如ClassPathXmlApplicationContext进行容器的启动时,可以通过实现BeanFactoryPostProcessor进行自己的一些自定义扩展,CustomEditorConfigurer便是spring在此基础上的扩展。
customEditors属性
获取conversionService实例
Caused by: java.lang.IllegalArgumentException: Cannot convert value of type 'study.ioc.applicationcontext.propertyeditor.DatePropertyEditor' to required type 'java.lang.Class' for property 'customEditors[java.util.Date]': PropertyEditor [org.springframework.beans.propertyeditors.ClassEditor] returned inappropriate value of type 'study.ioc.applicationcontext.propertyeditor.DatePropertyEditor'\tat org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:259)\tat org.springframework.beans.TypeConverterDelegate.convertToTypedMap(TypeConverterDelegate.java:608)\tat org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:183)\tat org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:585)\t... 17 more
conversionService属性
在该方法中完成了属性转移
不知道大家有没有遇到过,不知道在哪一个spring的版本,CustomEditorConfigurer中用于存储customEditors的类型变成了Class<? extends PropertyEditor>,而不是直接就是PropertyEditor,这样当我们在配置该属性customEditors时,如果使用value-ref或者直接使用bean标签配置自定义编辑器bean实例的引用,此时会发生类型转换错误(内容见后文),而需要使用value属性直接指定类路径就行了,spring会使用反射获取到对应的Class类型。个人觉得这是spring升级之后的一个bug,可能使用customEditors这种方式用的比较少,所以并不得到关注。如果要解决这个bug,我们通过扩展默认的ClassEditor编辑器,在其中增加实例转Class的逻辑
propertyEditorRegistrars属性
错误内容如下:
finishBeanFactoryInitialization
(2)新增一个属性编辑器登记实现类DatePropertyEditorRegistrar,实现org.springframework.beans.PropertyEditorRegistrar接口,实现其中的registerCustomEditors方法,将我们的自定义属性编辑器通过该方法注册到相应的编辑器注册类(在上面讲到的spring默认实现中,这个编辑器注册类就是BeanWrapperImpl,也就是PropertyEditorRegistrySupport)。
激活后处理
其实我们仔细想一想,BeanWrapperImpl继承了PropertyEditorRegistrySupport,BeanWrapperImpl是在我们加载bean实例的时候,用来封装bean的初始化实例的包装类,那自然这两个属性便是随着BeanWrapperImpl实例创建的时候设置进去的。跟踪代码发现,这个设置的地方是在AbstractBeanFactory的initBeanWrapper方法中。
二.如何实现定义的编辑器/转换器
invokeBeanFactoryPostProcessors
和SPEL的实现一样,属性编辑器/转换器也是在bean进行属性注入的时候执行的逻辑(这也是必然的),都是在AbstractAutowireCapableBeanFactory的applyPropertyValues方法中完成了对属性的解析转换。如下图,convertForProperty方法便是编辑器/转换器的执行入口:
FactoryBean<ConversionService>
通过afterPropertiesSet添加
和CustomEditorConfigurer不同,调试ApplicationContext的启动逻辑,AbstractApplicationContext的finishBeanFactoryInitialization方法是实现容器bean初始化操作的逻辑,在该方法中对conversionService进行了定制化的实现。
配置方式如下:
下面我们来具体讲一下CustomEditorConfigurer怎么来实现编辑器的注册。上面提到ApplicationContext在启动会对所有实现了BeanFactoryPostProcessor接口的类进行初始化操作,这个初始化操作便是调用该接口中的postProcessBeanFactory方法。CustomEditorConfigurer在该方法中完成了对propertyEditorRegistrars和customEditors属性的处理。如下图:可以发现该处理很简单,如果是propertyEditorRegistrars,则循环调用beanFactory的addPropertyEditorRegistrar方法,将propertyEditorRegistrar添加到beanFactory的propertyEditorRegistrars属性中;如果是customEditors,则循环调用beanFactory的registerCustomEditor方法,将customEditors添加到beanFactory的customEditors属性中。代码结束。是不是很疑惑,只不过是把CustomEditorConfigurer中的这两个属性转移到工厂中相同的两个属性上而已。从我们上面第一部分:一.属性编辑器/属性转换器在属性注入中的执行逻辑 中讲解的内容知道,我们需要的是将相关的编辑器注册到PropertyEditorRegistrySupport中,才能最终应用到bean的属性解析过程中。
(1)新增一个属性编辑器类DatePropertyEditor继承java.beans.PropertyEditorSupport(实现java.beans.PropertyEditor接口),重写其中的setAsText方法,实现String到Date的转换,如果使用的是第一种方式(customEditors属性注入),此时直接如上图配置就行了,无需进行其他操作。
总结:
我们继续进入到BeanWrapperImpl的convertForProperty的方法,又调用了父类的convertForProperty方法,然后在该方法中又直接调用了convertIfNecessary方法,如下图:此时你会发现,BeanWrapperImpl作为TypeConverter的功能已经结束了,而后面的工作委托给了TypeConverterDelegate代理类,如果我们要自定义一个TypeConverter,到最后大概也是要直接使用这个委托类的,当然如果你完全不怕麻烦,也可以自己实现委托中的逻辑。
CustomEditorConfigurer
新增一个String2IntConverter类,实现一个org.springframework.core.convert.converter.Converter接口:
InitializingBean
FactoryBean的作用与实现原理
converters属性
和属性编辑器一样,上面的处理过程只是将conversionService添加到了BeanFactory中,没有真正注册到我们的PropertyEditorRegistrySupport中,而同样和属性便器一样,也是在一个地方(AbstractBeanFactory的initBeanWrapper方法中)完成了将conversionService注册到PropertyEditorRegistrySupport。结束。
扩展:
AbstractApplicationContext启动过程
一.属性编辑器/属性转换器在属性注入中的执行逻辑
从上图逻辑可以看出来,1.先是判断是否存在名称为CONVERSION_SERVICE_BEAN_NAME的bean,这个CONVERSION_SERVICE_BEAN_NAME的值就是conversionService,因此我们在配置文件中配置转换器时,id必须是conversionService,所以说他是定制化的实现。2.然后从BeanFactory中获取conversionService的bean实例,最后设置到BeanFactory的conversionService属性中。 结合我们刚刚讲到的,ConversionServiceFactoryBean实现了InitializingBean接口,因此在获取bean实例的过程中,完成bean实例的构造以及属性注入之后,会对bean的实例进行初始化操作,其中便会对实现了InitializingBean接口的类进行调用其afterPropertiesSet方法,也就是在该方法中,完成了将我们自定义的转化器注册到其属性conversionService中,然后通过FactoryBean的getObject方法返回真正的conversionService属性3.将得到的conversionService设置到beanFactory的conversionService属性中。可能有同学要问,为什con么要专门实现InitializingBean接口,来进行将我们自定义的转换器添加到conversionService属性中的工作,而不是直接在FactoryBean的getObject方法中去做就行了,这样不更省事吗?这样做原则上没什么问题,但是熟悉bean实例加载过程的就知道,如果我们定义的FactoryBean不是单例的,FactoryBean的getObject方法会在每次我们获取bean的时候都去执行一遍,而这对于conversionService来说是没有必要的。
1.自定义属性编辑器
2.自定义属性转换器
实现getObject方法
AbstractBeanFactory
有这样一个场景,当我们的类的属性是一个Date类型的对象时,我们怎么在配置文件中配置这个属性的value值,当然我们可以选择将Date也作为一个bean配置,然后通过bean引用就行了。但这样类似的需求其实很多,如果都这样做未免为导致配置文件相当繁琐。 spring提供了属性编辑器/转换器的功能,除了自身已经实现一些默认的转换功能,还支持用户进行自定义,上面的场景正式一个属性转换的过程,将2020-3-9这样的字符串转换为Date类型,然后进行属性注入。接下来让我们从具体的例子开始,然后结合源码去讲解其实现原理。
然后配置方式如下:
进入到AbstractAutowireCapableBeanFactory的convertForProperty的方法,如下图。这里对BeanWrapperImpl简单介绍一下,BeanWrapperImpl是对BeanWrapper接口默认实现,BeanWrapper接口设计的目的很简单,就是用来封装BeanInstance(bean的初始化实例,此时只是进行简单构造器等初始化),然后获取该实例的属性描述信息(如属性类型等)。但BeanWrapperImpl同时还间接的继承了TypeConverterSupport(TypeConverter的默认实现类),因此BeanWrapperImpl本身也是一个属性类型转换器。从下图的代码便可以看出来,如果传入的TypeConverter就是BeanWrapperImpl,则使用BeanWrapperImpl进行解析。
当然,如果我们不想要用BeanWrapperImpl去作为TypeConverter解析怎么办,如下图代码,在进入AbstractAutowireCapableBeanFactory的convertForProperty的方法之前,有一个获取自定义类型转换器的地方,只要在这个地方实现我们自己的类型转换器注入就行了。
spring提供了一个自定义编辑器配置类org.springframework.beans.factory.config.CustomEditorConfigurer,其提供了两个属性:propertyEditorRegistrars和customEditors,两个属性都可以实现自定义编辑器的配置。
实现afterPropertiesSet方法
在这里来吹嘘一下spring,spring的伟大之处便是其牛逼的架构模式,spring搭建了一个基础的BeanFactory实现IOC容器功能,在其中内置了很多开放式的扩展点,允许用户通过这些扩展点实现自定义的功能。而spring自身也在基于这些基础的扩展点进行扩展提供给用户更强大的功能,比如ApplicationContext的实现,又比如ApplicationContext利用BeanFactoryPostProcessor提供了扩展点,而CustomEditorConfigurer基于此扩展点提供了自定义属性编辑器的扩展功能,而用户则在该扩展功能上完成自定义属性编辑器的定义。这是一套完美的自我扩展和自我演进式的架构,这便是spring ioc的牛逼之处。
上图已经对个属性进行了一定说明,这里就不做解释了,从PropertyEditorRegistrySupport中我们发现,defaultEditors是没办法进行重构的,他是通过类自身的方法createDefaultEditors进行填充的。其他的属性基本上都提供了公共方法入口,如果我们从这些公共方法入口去反推的话,就太麻烦了。我们还是从应用实例去推进吧。
ConversionServiceFactoryBean
实现postProcessBeanFactory方法
BeanFactoryPostProcessor
以上就是全部的使用方式,接下来我们讲一下ConversionService的实现机制。如下图:ConversionServiceFactoryBean是一个FactoryBean,并且实现了InitializingBean接口,关于FactoryBean请参考:
属性设置
0 条评论
回复 删除
下一页