9.原型bean和FactoryBean
🍑 9.原型bean和
FactoryBean
在上一节中,我们实现了向bean中注入容器资源的功能,注入时不用关心配置文件中,需要什么就实现什么资源接口即可,之后资源的注入统一在初始化时执行。这使得bean的功能更强大也更灵活。但是至今为止,我们还只是创建单例的bean,没有实现如何创建原型模式的bean,并且bean的创建只能从配置文件中获取,一旦bean涉及到的配置太多,xml文件的编写就会异常复杂,是否可以使用更方便的java代码编写bean的配置并控制bean的实例化呢?所以本节中有两个目标:
- 创建多种模式的bean(单例或者原型)
- 以多种方式创建bean对象(xml或者java代码)
本节涉及到的代码我放到了 仓库 中
原因
为了创建多种类型的bean对象,我们将createBean
的代码进一步改进,增加bean的注册信息,使其包含当前bean的模式,如果当前bean是单例模式,那么我们将其创建出来并保存到容器中方便下次直接调用,保持bean的单例性,如果当前bean是原型模式,那么每次我们都新建并不保存这个bean,这样可以保证每次都是新的bean对象,从而保证bean的原型性质,总结起来,为了实现第一个目标,做了两点改变:
修改bean的注册类
BeanDefinition
,使其拥有模式这个状态,标识当前bean是单例还是原型:修改创建方式
createBean
的代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
@Override protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException { Object bean = null; try { bean = createBeanInstance(beanDefinition, beanName, args); // 给 Bean 填充属性 applyPropertyValues(beanName, bean, beanDefinition); // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法 bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("Instantiation of bean failed", e); } // 注册实现了 DisposableBean 接口或xml中配置了destory-mothoid的 Bean 对象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE来决定是否保留当前bean if (beanDefinition.isSingleton()) { addSingleton(beanName, bean); } return bean; }
以上实现多种模式的bean对象的流程如下,相比于非单例模式的创建,单例模式只是多了一步保存到容器中的过程:
为了将复杂bean创建过程中繁杂的xml配置转换成java代码,我们引入了几个类,核心就是
FactoryBean
类,如果一个bean想要不通过配置文件创建,那么就需要继承这个类,然后将创建逻辑在这个类中的getObject
方法中实现,编码完毕之后,将这个FactoryBean
对象交给配置文件来管理,配置文件只创建FactoryBean
对象,内部真正包裹的bean对象暂时不用管 在
doGetBean
的过程中,不管是首次创建获得的bean对象还是直接从容器中拿到的bean对象,拿到之后都包裹了一个getObjectForBeanInstance
方法,目的是为了拿到真正需要的bean对象,因为上面说到,如果bean的创建被移动到FactoryBean
中的getObject
方法中时,配置文件创建的就是FactoryBean
对象,而不是内部真正包裹的bean对象,所以在此时需要拿到这个真正的bean,也就是此时获取bean时在getObjectForBeanInstance
方法中有两种情况:- 当前bean使用xml配置:此时直接返回,已创建的就是需要的
- 当前bean是
FactoryBean
的对象:此时需要返回内部包裹的真正的bean对象,也就是调用其getObject
方法,如果当前factoryBean是单例的,还需要将其内部真正的对象保存到factoryBean
的缓存容器中,后期从factoryBean
中获取真正的bean对象时防止重复创建破坏单例性 - 当前bean是
FactoryBean
的话,每次getBean使用这个bean,最后一步都会将内部真正的bean拿出来,可能缓存中有,此时直接拿,缓存中没有调用getObject方法重新创建
getObjectForBeanInstance
方法内部做了类型判断,如果是普通的bean对象,那么就是目标bean对象,不用任何处理直接返回,如果是工厂bean对象,那么首先尝试从缓存中拿,否则就调用其中的getObject
方法,拿到真正的bean对象之后判断是否需要保存到缓存中,最后返回,具体的创建过程如下图所示: 可以看出,只是加入了一个
FactoryBean
,内部包装了一个真正的bean,如果真存在这种类型的bean,将内部包装的bean获取到就可以了,获取时如果是单例的bean,那么需要先从缓存中获取,缓存中没有创建之后需要将其保存到缓存中再返回,如果是原型的bean,此时直接创建并返回,与缓存就没有关系了
思路
为了实现非单例模式的bean创建,我们在注册的时候增加模式信息用来保存当前bean是否是单例模式,创建时根据这个模式信息来判断bean是否保存,不是单例模式的bean不保存,下次使用时从容器中获取不到,就会直接重新创建,创建的过程中还是不保存当前bean,这样每次都是重新创建,自然就不是单例模式的bean对象了,总结起来有两点,一是修改bean注册信息,二是非单例模式bean不保存
为了实现bean的第二种创建方式,经历了以下几个流程:
- 定义一个类,实现
FactoryBean
接口,在getObject
内部创建真正的bean,然后将这个实现FactoryBean
接口的类交给xml文件管理,这个类并不是真正的bean,而是一个外壳 - 正常执行项目的流程,
doGetBean
时会根据配置文件创建bean对象,此时这个bean对象有可能是配置文件中创建的普通bean,也有可能是实现了FactoryBean
的外壳,所以需要将这两种类型统一处理,也就是调用getObjectForBeanInstance
方法·,之后返回的bean就是真正需要的 getObjectForBeanInstance
方法内部判断是普通的bean还是外壳,通过类型判断,普通的bean直接返回,外壳需要将内部真正的bean拿出来,需要经历以下几步:- 先从缓存中尝试获取真正的单例bean
- 获取到直接返回真正的bean
- 没获取到要么是当前bean不是单例,要么当前bean是单例但是第一次获取,不管如何到这一步都要新创建bean
- 此时调用
getObjectFromFactoryBean
方法,根据内部调用getObject
方法获取真正的bean,根据是否是单例决定是否保存到缓存中 - 不管是否保存到缓存中,这一步新建的bean都需要返回
- 获取到所有真正的bean之后,执行自己的业务
总结来看,bean的第二种创建方式就是将复杂的xml配置移动到了getObject
方法中用java代码代替,之后将这个外壳bean使用xml简单配置加入ioc容器中,并且在获取bean对象时增加一步getObjectForBeanInstance
的判断,从而拿到真正的bean对象,而不是FactoryBean
类型的外壳,最终项目的整体结构为:
类的变化
新增的类
FactoryBean
:是一个接口,实现这个接口的类在内部的getObject方法中定义真正bean的创建方式,这个类本身只是一个外壳,交给xml文件管理FactoryBeanRegistrySupport
:在bean的生命周期中加入的一个类,加入之前的继承图为:加入之后的继承图为:
在继承链中新增一个类的目的是为了从继承
FactoryBean
的外壳中获取真正的bean对象,对于单例的bean,还设置了一个缓存容器来保存真正的bean对象,当缓存中没有时,需要调用外壳中的getObject
方法得到真正的bean对象,同时保存单例bean到缓存中,类的结构图为:上面的三个方法就可以实现从factorybean中获取到真正需要的bean对象,并且将单例的bean保存到缓存中便于后期获取
修改的类
BeanDefinition
:修改bean的注册信息,新增一个模式状态信息,目的是为了保存bean的模式信息,从而根据模式信息创建不同类型的bean,这里创建不同模式的bean其实就是保不保存,不保存的bean就会每次都重新创建,从而实现原型模式,保存的bean每次获取的都是同一个bean,从而实现单例:XmlBeanDefinitionReader
:由于bean的注册信息多了一项,所以可以在xml配置文件中指定模式状态,于是xml文件读取时就需要加入读取模式状态信息的代码,最新的读取bean的注册信息的代码为:1 2 3 4 5 6 7 8 9
// 解析标签 Element bean = (Element) childNodes.item(i); String id = bean.getAttribute("id"); String name = bean.getAttribute("name"); String className = bean.getAttribute("class"); String initMethod = bean.getAttribute("init-method"); String destroyMethodName = bean.getAttribute("destroy-method"); //新增的一个属性,当其不为空时注入到bean的注册信息中 String beanScope = bean.getAttribute("scope");
AbstractAutowireCapableBeanFactory
:为了创建非单例模式的bean,在创建时需要判断状态,不是单例模式的bean不保存,从而下次获取无法从容器中获取,只能新建,达到原型模式的特点,为了实现这一点,改变了createBean
方法中的代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
@Override protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException { Object bean = null; try { bean = createBeanInstance(beanDefinition, beanName, args); // 给 Bean 填充属性 applyPropertyValues(beanName, bean, beanDefinition); // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法 bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("Instantiation of bean failed", e); } // 注册实现了 DisposableBean 接口的 Bean 对象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE来决定是否保留当前bean if (beanDefinition.isSingleton()) { addSingleton(beanName, bean); } return bean; }
另外当bean对象不是单例模式时,这个bean不保存销毁策略,也就是说暂时不执行销毁策略,这里是因为原型的bean不受spring容器管理,即使保存销毁逻辑后期也无法触发销毁逻辑的执行:
AbstractBeanFactory
:这个类从继承DefaultSingletonBeanRegistry
变为继承FactoryBeanRegistrySupport
,目的是为了增加从外壳中获取真正bean对象的功能,而这个类的改变是将获取到的bean对象进一步处理,如果是外壳的话,还需要进一步处理,这个类中做了以下几步处理:在
doGetBean
中将获取到的bean(从容器中或者首次创建)放到getObjectForBeanInstance
中进一步处理在
getObjectForBeanInstance
方法中针对实现FactoryBean
的这种外壳bean进行特殊处理,利用继承自FactoryBeanRegistrySupport
中的方法来获取到其中真正的bean,具体的代码为:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
private Object getObjectForBeanInstance(Object beanInstance, String beanName) { //普通的bean,不是外壳bean,直接返回 if (!(beanInstance instanceof FactoryBean)) { return beanInstance; } //在这里说明当前的bean是一个实现FactoryBean的外壳bean //从缓存中尝试获取工厂bean对象,能获取到肯定是单例 Object object = getCachedObjectForFactoryBean(beanName); //获取不到要么是没有,要么是非单例 if (object == null) { FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance; //调用这个方法,调用getObject方法创建真正的bean对象 //是单例对象还需要将其保存到缓存中 object = getObjectFromFactoryBean(factoryBean, beanName); } return object; }
DefaultSingletonBeanRegistry
:新增了一个常量,给继承他的FactoryBeanRegistrySupport
类使用:
经历以上几步就可以得到真正需要的bean对象,之后就可以拿这些bean对象执行自己的业务,核心就是为了将bean的创建变为使用java代码,从而进行了一些改变,目的是为了拿到这些真正的bean
bean的创建和获取
经历上面的分析,已经知道了bean的不同创建方式是如何最终得到统一的bean对象的,下面采用debug的方式来介绍新的bean的创建和获取方式,项目中有两个bean对象,userService
是普通的bean,注册信息在xml文件中配置,userDao
的注册信息的配置转移到了实现FactoryBean
接口中的getObject
方法中,xml配置文件中配置的是实现FactoryBean
接口的类信息,也就是xml文件中保存的是外壳,下面介绍在这种背景下如何获取bean对象
配置xml文件,其中
userDao
的部分变成了配置实现FactoryBean
的部分,也就是userDao
的创建过程放到了FactoryBean
中:读取配置文件,进入
refresh
方法中,执行前面五步:实例化所有的对象,最终调用的
doGetBean
方法中,首次获取调用createBean
方法之后,此时容器中已经存在了一个bean对象,需要经过getObjectForBeanInstance
方法拿到这个bean对象内部真正的bean对象:此时可以对比
createBean
中保存的bean对象和当前经过getObjectForBeanInstance
方法获取到的bean对象之间的区别,两个的名称都叫做proxyUserDao
,但是里面保存的内容是不一样的:singletonObjects
中保存的是xml配置文件中创建出来的bean对象,就是一个实现了FactoryBean
的外壳,factoryBeanObjectCache
中保存的是外壳bean中使用getObject
方法创建出来的真实bean对象,后期获取时,只要获取这个procyUserDao
,那么就会从factoryBeanObjectCache
中获取真正的bean,因为获取bean的操作会最终经过一步getObjectForBeanInstance
,会将外壳bean中真正的bean取出来实例化完成之后,就可以使用bean的名称获取到bean,进行业务操作了,对于本项目来说,实例化完成之后,只有单例的bean对象被保存了,原型模式的bean每次都需要新建,最终项目的执行结果为:
userDao
的创建被移动到了一个实现FactoryBean
的java类中,不管是xml文件中配置bean的注册信息进行创建还是在java类中进行创建,都可以得到bean对象为了测试单例模式和原型模式是否奏效,进行了单元测试,最终的结果为:
其中userDao是单例模式,所以地址是一样的,userService是原型模式,即使内部的内容一致,地址也不一致,所以最终两个userService不会相等
总结
本节中实现了两个目标,第一个目标是可以创建原型模式的bean对象,实现方式就是优化了bean的注册信息,增加了一个模式来标记当前bean是单例还是原型模式,第二个目标是增加了一种从java类中创建bean对象的方式,这种方式将创建bean的方式从xml配置移动到了java类中,xml配置中只配置实现FactoryBean
的外壳,最终拿到xml配置中的bean之后,需要增加从这个bean中拿到真正bean的过程,项目中新增的类图结构为:
核心就是在继承链中新增了一个FactoryBeanRegistrySupport
类,在其中从外壳bean中调用其getObject
方法得到真正的bean,并把单例的bean保存到缓存中
针对原型bean来说,不能像正常的bean一样执行销毁逻辑,因为原型bean都没有交给spring容器管理,所以不再需要注册原型bean的销毁逻辑,想要销毁原型bean需要自己手动销毁