Small_spring16
🍖 small_spring16
在之前的章节中,我们实现了spring
中最重要的IOC
和AOP
的相关核心功能,并且利用三级缓存解决了循环依赖的问题,但是在属性填充的过程中,一直都是直接将字符串传递给bean中对应的属性,这会导致一些类型转换的问题,并且并不是所有属性都是string
的类型,所以本节中我们将会解决bean属性填充中类型转换的问题,以applyPropertyValues
方法为入口,相关代码我放到了
仓库
中
原因
在给bean进行属性填充时,之前的操作都是填充字符串,而这不符合真实情况,真正的bean内部的属性多种多样,而配置时一般都配置的是String
类型的变量,这就需要将String
类型的属性值转换成目标类型的属性值,于是提出了类型转换模块,并在现有的模块中引入类型转换的内容,实现属性值的转换
思路
为了引入类型转换服务,一共经历了下面几步:
首先需要在xml文件中配置类型转化服务的相关模块,便于项目中能够感知到并且调用这些服务
在
refresh
方法中执行完所有的方法,对所有的bean进行实例化的时候,此时先判断当前项目中是否配置了类型转换的服务,这里是按照名称判断,代表后面的项目在使用类型转换服务时,对应的类型转换服务的beanName
都是统一的名称如果存在类型转换服务,那么就调用
getBean
方法创建并拿到这个类型转换服务的bean对象,如果这个bean对象内部还需要一些其他的属性,在创建过程中会一并填充类型转换服务的属性填充完毕之后,触发了这个bean的初始化方法
afterPropertiesSet
,内部创建了一个DefaultConversionService
类型转换服务的对象,并且将xml配置文件中配置的类型转换器保存到这个类型转换服务的converts
容器中便于后期使用之后将这个拿到的类型转换服务的对象保存到
AbstractBeanFactory
类中的conversionService
中,后期属性填充时直接拿到这个类型转换服务中的类型转换器来进行类型转换,也就是拿到着之前保存到DefaultConversionService
的converts
中的类型转换器执行其余的正常bean的创建过程,重点在属性填充的过程中,下面以
husband
的创建为例介绍如何进行类型的转换:- 属性填充时拿到当前bean的所有需要填充的属性,如果当前属性依赖其他的bean,那么就调用
getBean
获取到这个所依赖的bean对象,然后进行属性填充 - 如果当前属性是一个普通属性,那么就拿到这个属性的真正类型,也就是
targetType
- 根据拿到的
targetType
和sourceType
来尝试找到匹配的类型转换器 - 调用类型转换服务中封装的
convert
方法将当前的value
从sourceType
转换成targetType
完成类型转换 - 进行属性填充
- 属性填充时拿到当前bean的所有需要填充的属性,如果当前属性依赖其他的bean,那么就调用
完成bean的创建,执行自己的业务逻辑
核心就是调用
DefaultConversionService
内部的一些方法实现类型的转换类型转换服务工厂中保存了类型转换服务的对象
类型转换器工厂中保存了所有的类型转换器
类型转换服务工厂的初始化方法触发时会创建一个类型转换服务的对象,然后将类型转换器工厂中的所有的类型转换器保存到这个类型转换服务中的
converts
容器中,后面调用这个容器中的类型转换器就可以完成对类型的转换
类的变化
新增的类
Converter
:类型转换器需要实现这个接口,具体的类型之间的转换逻辑在内部的convert
方法中定义,例如可以将String
类型的数据转换成LocalDate
类型的数据ConverterFactory
:实现这个接口的类可以根据其内部的getConverter
方法得到一个类型转换器,相当于内部对类型转换器进行了封装,对外提供统一的接口:ConverterRegistry
:可以将转换器注册保存到一个容器中,然后外部使用时直接调用容器中注册保存的转换器使用即可:GenericConverter
:核心就是这个接口内部的ConvertiblePair
类。保存了一个源类型和一个目标类型,后期拿这个类做保存转换器的容器的键,从而针对不同的类型组合有不同的键,内部还提供了一个convert
方法,最后在使用转换器的功能时,都是调用这个convert
方法,内部调用转换器的convert
方法来完成类型的转换:ConversionService
:实现这个接口需要实现两个方法,分别判断给定的两个类型之间是否可以转换,以及对外提供调用的convert
接口,在这个convert
接口中调用前面的getConverter
方法得到转换器,然后再调用GenericConverter
接口中的convert
方法,内部进一步调用转换器的convert
方法执行类型的转换,相当于对类型转换服务做了封装,一个convert
方法经过了三次封装:GenericConversionService
:类型转换服务的核心类,内部定义了一个容器,可以向其中保存所有的转换器,也可以从容器中拿到想要使用的转换器进行类型的转换。除了这个容器之外,还有一些辅助的方法可以保存类型转换器,获取类型转换器,触发类型转换器:DefaultConversionService
继承了GenericConversionService
类,内部指定默认的转换器是String
转成Number
的转换器:ConvertersFactoryBean
:是一个FactoryBean
的子类,内部调用getObject
方法可以得到配置的所有的类型转换器,也就是说这个类中封装了所有的类型转换器,这里需要自己手动定义好类型转换器,然后在配置到这个类中:ConversionServiceFactoryBean
:xml配置文件的入口,内部保存了一个通用的类型转换服务GenericConversionService
的对象,在初始化方法中初始化这个对象,并且将其在xml文件中依赖的ConvertersFactoryBean
中的所有类型转换器保存到类型转换服务GenericConversionService
的对象中,相当于本类只是将类型转换器保存到类型转换服务中:NumberUtils
:内部可以将String
转换成指定的Number
类型,一共有Byte
,Short
,Integer
,Long
,BigInteger
,Float
,Double
,BigDecimal
八种类型可以转换:StringToNumberConverterFactory
:内部定义了一个将String
转换成Number
的转换器,主要是调用NumberUtils
中的转换方法完成对类型的转换:StringToLocalDateConverter
:内部定义了一个将String
转换成LocalDate
的转换器,这个类最终在类型转换器工厂中保存,最后在类型转换服务工厂中被保存到了类型转换服务中的converts
的容器中便于后期使用:
修改的类
ConfigurableBeanFactory
:新增了获取和保存类型转换服务的待实现方法:BeanFactory
:新增一个待实现的containsBean
方法,判断bean注册信息中有没有指定的bean注册信息:AbstractBeanFactory
:新增了上面提到的三个方法的实现,并且新增一个ConversionService
类型的变量存储xml中配置的类型转换服务:AbstractApplicationContext
:修改refresh
中实例化全部bean的方法,在其中添加从xml配置文件中获取类型转换服务并保存的功能,从原来的preInstantiateSingletons
变成了现在的finishBeanFactoryInitialization
:1 2 3 4 5 6 7 8 9 10 11 12
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // 设置类型转换器 if (beanFactory.containsBean("conversionService")) { Object conversionService = beanFactory.getBean("conversionService");//拿到注册的类型转换服务 if (conversionService instanceof ConversionService) { beanFactory.setConversionService((ConversionService) conversionService); } } // 提前实例化单例Bean对象 beanFactory.preInstantiateSingletons(); }
AbstractAutowireCapableBeanFactory
:在applyPropertyValues
方法中引入类型转换的功能,从之前保存的类型转换服务中判断是否可以转换,可以转换的话,调用对应的转换器从而实现类型转换的功能1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
protected void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) { try { //。。。 // 类型转换,重点就是在这里 else { //需要什么类型,当前是什么类型 Class<?> sourceType = value.getClass(); //按照属性名从bean中拿到这个属性真正的类型是什么 Class<?> targetType = (Class<?>) TypeUtil.getFieldType(bean.getClass(), name); ConversionService conversionService = getConversionService();//拿到刚才保存的类型转换服务 if (conversionService != null) { if (conversionService.canConvert(sourceType, targetType)) { //将填充的属性转换成真正需要的类型 //比如将String转换成int value = conversionService.convert(value, targetType); } } } // 反射设置属性填充 BeanUtil.setFieldValue(bean, name, value); } } catch (Exception e) { throw new BeansException("Error setting property values:" + beanName + " message:" + e); } }
重点就是在普通属性填充时,新增了类型转换
经过上面类的新增和修改,顺利的在属性填充的过程中引入了类型转换的功能,xml文件中以两个工厂类为入口,一个是类型转换服务工厂,其中保存了一个类型转换服务的变量和一个容器,容器中保存了所有注册的类型转换器。在初始化方法中进·行初始化这个类型转换服务的变量,然后将容器中所有的类型转换器注册到类型转换服务中的converts
容器中,后期统一调用类型转换服务中的converts
获取到匹配的类型转换器使用即可
bean的创建和获取
下面从bean的创建和获取的角度来说明项目中如何引入类型转换模块,项目中有一个husband
的bean对象,内部有两个属性,其中marriageDate
属性的类型是LocalDate
,需要进行类型转换,为了引入类型转化模块,首先在xml文件中配置了类型转换服务工厂的bean以及类型转换器工厂的bean,下面介绍bean创建和获取的流程,重点介绍类型转换的过程:
读取配置文件,得到xml文件中配置的bean的注册信息:
执行
refresh
方法中其他的方法,并实例化所有的bean对象,现在的实例化所有对象的方法被封装到了finishBeanFactoryInitialization
方法中:获取到类型转换服务工厂对象,一共经历下面几步:
创建空的类型转换服务工厂对象:
将其保存到第三级缓存中,并且执行属性填充,主要是填充xml配置文件中指定所依赖的类型转换器工厂,类型转换器工厂中保存的是一个
String
转换成LocalDate
的转换器:执行初始化前的逻辑之后,执行初始化方法:
初始化方法内部先创建一个默认的类型转换服务对象:
创建默认类型转换服务对象会初始化当前类型转换服务工厂中的
conversionService
属性,而创建这个默认的类型转换服务对象调用其内部的无参构造,无参构造中向类型转换服务中的容器中保存了一个默认的转换器,是String
转换成Number
的转换器:创建完默认的类型转换服务对象之后,调用
registerConverters
方法将当前工厂对象中的converts
中从xml文件中读取到的所有类型转换器保存到默认类型转换服务中的converts
:完成初始化,执行初始化后的逻辑,尝试给当前的类型转换服务工厂对象进行
AOP
,并将其保存到单例池中,完成bean对象的创建:
将获取到的类型转换服务工厂对象保存到当前项目中,便于后期使用:
实例化所有的bean对象,主要是
husband
的实例化,一共经历下面几步:创建空bean:
将其保存到三级缓存中,然后对空bean进行属性填充,这是最重要的步骤,在这里引入类型转换的过程:
填充第一个属性,拿到的值本身是
String
,要被填充的属性需要的类型也是String
,所以不需要类型转换,直接填充:填充第二个属性,拿到的值本身是
String
,要被填充的属性需要的类型是LocalDate
,所以需要进行类型转换:拿到上面保存的类型转换服务,先判断两个属性能否转换,可以的话再根据内部的转换器进行转换,最后进行属性填充:
属性填充完成之后,执行后续的步骤,然后将其保存到单例池中,后续从单例池中拿到这个bean对象执行自己的业务逻辑,执行
toString
方法的结果如下,可以发现类型确实被转换成功:
总结
在本节中我们引入了类型转换模块,改进了bean属性填充的功能,从而使得bean可以拥有各种类型 的数据,但是前提是这些类型转换器需要自己手动编写并且配置到类型转换器工厂中,之后就可以愉快的进行类型转换了,xml的入口类有两个,第一个是类型转换器工厂类,内部将所有的类型转换器注册到一个set中返回,第二个是类型转换服务工厂类,内部将上面的类型转换器工厂类注入,并且定义一个类型转换服务对象,从而将注入的类型转换器工厂中的所有类型转换器转移到类型转换服务的converters
容器中保存,从而便于项目使用