4.bean的实例化和属性填充分离
🍐 4.bean的实例化和属性填充分离
本节中主要是修改上一节中留下来的小问题,上一节中实现了带参的bean对象创建,但是一旦当前bean对象依赖另一个bean对象时,这种创建方式就会失效,因为上一节的带参bean对象创建需要先得到所有的参数。也就是说,当内部依赖另外一个bean对象时,需要先将这个bean对象new出来,然后将其当成参数传递给getBean方法,但是这又不符合bean对象的设计模式了,我们前面千辛万苦将bean的创建交给了IOC容器,现在不能将bean的创建工作交给自己,所以这里需要另辟蹊径,来实现bean的另外一种创建模式(先创建再属性填充),具体的代码在 仓库 中
原因
由于spring中,bean与bean之间经常存在依赖关系,最典型的就是service中的bean肯定会依赖于dao中的bean,也就是说,service中的bean有一个参数,参数类型是另一个bean。上一节中我们实现了带参bean对象的创建,但是创建bean对象的时候,需要将这个bean对象需要的所有参数都准备好,这就出现了一个问题:
创建service中的bean时,需要将dao中的bean准备好,也就是提前需要得到dao中的bean对象,这不符合需要时才创建的逻辑,所以我们在这一节中就将解决这个问题
为了实现使用时才创建,需要改变思路,上一节中是将所有的参数准备好,然后直接创建bean对象,这一节中将bean对象依赖的所有关系都记录下来,普通类型的参数直接记录值,bean类型的参数只记录一个依赖关系,然后先创建一个空的bean对象,之后再进行属性填充,这种创建bean的方式才更加符合原始spring框架的流程,具体如下图所示:
其实这种依赖关系可以进行配置,也就是这种依赖的属性列表可以配置到xml文件中,然后从xml配置文件中读取到对应的PropertyValues
上一节中我们创建bean的时候,使用getBean函数需要传递bean需要的参数,这样才能创建带参的bean对象,这一节中我们不传递任何参数,只是将需要bean的参数的信息保存到一个类中,先创建空的bean对象,然后再进行属性填充,新的项目结构为:
不再是创建bean时就填充属性,而是创建好了之后再填充属性,这样就不用在getBean时就传递已经实例化的bean属性了,也就是getBean("userService")
时不需要实例化userDao
的bean对象,只有内部真正需要的时候才创建
在注册bean的时候,不仅保存其类信息,还保存其所有依赖的属性,便于后期属性注入
思路
为了将bean的定义和属性的填充分开,本章中对createBean
的方法进一步进行了增强,在上一节中为了实现带参bean的创建,在createBean
方法中引入了createBeanInstance
方法,根据传递来的参数列表匹配到对应的构造函数从而创建bean对象,本节中进一步对createBean
方法进行增强,先调用createBeanInstance
方法创建一个空的bean对象,然后利用bean注册时保存的propertyValues
来进行属性填充,改动的地方如下图蓝色区域所示:
也就是先创建空bean,然后根据属性依赖关系再进行属性填充,为了实现属性填充,本节中又添加了三个新的类,并且在原来项目的基础上对一些类进行了修改,下面具体描述哪些类做了修改,并且新增了哪些类
类的说明
BeanReference
:如果某一个bean依赖另外一个bean对象,那么在记录这个bean的所有属性时,被依赖的bean会以这个类的对象出现,内部存储的是这个被依赖的bean的名称,这个类主要起到的作用是记录bean之间的依赖关系,当一个bean被依赖时,不会立马创建bean对象,类的结构为:PropertyValue
:这个类中保存了当前bean中的某一个参数的信息,包括参数的名称,参数的值,也就是这个类中有两个属性,类的结构如下:当当前bean的属性是普通属性时,name保存这个属性的名称,value保存这个属性的值,当当前bean的属性是另外一个bean时,例如service中的bean依赖与dao中的bean。那么name保存这个属性的名称,value保存的不再是dao中bean对象,而是保存一个
BeanReference
对象,内部存储了dao中bean对象的名称,相当于name指明这个属性的名称,BeanReference
内部指明这个属性依赖的是哪个bean,这样做的目的是为了减缓bean的创建时机PropertyValues
:这个类保存了当前bean的所有属性,每一个属性都保存在一个PropertyValue
类中,将所有的属性保存到一个List<PropertyValue>
中,在注册bean的时候,不再是单单保存bean的类信息,连同这个容器也一起注册到BeanDefinition
中,其中保存了当前这个bean的所有属性,这也说明BeanDefinition
这个类要做出修改,之后在属性填充时,会将其一个一个的遍历并进行属性的填充BeanDefinition
:对这个类做出修改,不再是只保存当前这个bean的类信息,还保存当前这个类的所有属性信息,保存到一个propertyValues
对象中的List<PropertyValue>
中,新的类结构如下:AbstractAutowireCapableBeanFactory
:上一节中介绍在这个类中实现创建带参bean对象的功能,但是本节中将带参bean对象的创建分为两步,第一步是创建空的bean,第二步是属性填充applyPropertyValues
,这个方法是新增的,相当于不再在第一步就创建带参对象,新形成的类结构为:内部利用bean注册时保存的属性列表
PropertyValues
来逐一填充,下面进行测试,详细代码在 仓库 中对于第一步来说,debug的结果如下,可以看出刚创建好的bean还没有属性填充进去,并且传递的参数args也是空:
对于第二步来说,debug的结果如下,普通属性直接填充,bean属性先创建再填充,这里判断是否是bean属性的依据是当前属性是否是一个
BeanReference
类型的变量:
经历上面的改造,整个项目的框架就搭好了,那么项目中是如何具体实现bean的实例化和属性填充分开的呢,我们接下来介绍
bean的创建和获取
在上一节中,我们在获取bean的时候,将bean的参数也传递了进去,也就是getBean的时候,同时传递参数,本节中对此进行以下几点改变:
getBean
不再传递参数,而是将参数保存到PropertyValues
类中bean实例化时一直调用无参构造,使得bean创建之后,属性都为空
调用
createBeanInstance
方法创建bean对象之后,得到一个空对象,然后再进行属性填充,属性填充的代码为:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
protected void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) { try { //根据注册时保存的属性拿到当前bean的属性列表 PropertyValues propertyValues = beanDefinition.getPropertyValues(); //遍历属性列表 for (PropertyValue propertyValue : propertyValues.getPropertyValues()) { String name = propertyValue.getName(); Object value = propertyValue.getValue(); //属性为bean时单独处理,否则直接填充 if (value instanceof BeanReference) { // A 依赖 B,获取 B 的实例化 BeanReference beanReference = (BeanReference) value; //在这里才创建属性中的bean value = getBean(beanReference.getBeanName()); } //依次填充每一个属性 BeanUtil.setFieldValue(bean, name, value); } } catch (Exception e) { throw new BeansException("Error setting property values:" + beanName); } }
第10~15行是最关键的代码,这一段代码中描述的是,如果当前bean依赖的是另外一个bean(取出的属性值是一个
BeanReference
类型的对象),此时才转去获取当前这个bean对象,内部调用getBean
方法,还是按照之前分析的思路,先创建空bean,然后再属性填充,如果属性填充的过程中又依赖bean,那么再临时创建这个bean对象,继续执行先创建空bean,再属性填充的操作,具体的流程如下图,可以看到多了一步属性填充的操作: 上面描述了创建bean的过程,分为创建空bean和属性填充,那么外部是如何获取这个bean的呢,给出一段测试的代码,详细的代码在 仓库 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
@Test public void test_BeanFactory() { // 1.初始化 BeanFactory DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // 2. UserDao 注册,由于userDao没有属性,所以不用设置属性 beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class)); // 3. UserService 设置属性[uId、userDao] PropertyValues propertyValues = new PropertyValues(); propertyValues.addPropertyValue(new PropertyValue("uId", "10001")); //这里的value是BeanReference propertyValues.addPropertyValue(new PropertyValue("userDao",new BeanReference("userDao"))); // 4. UserService 注入bean BeanDefinition beanDefinition = new BeanDefinition(UserService.class, propertyValues); beanFactory.registerBeanDefinition("userService", beanDefinition); // 5. UserService 获取bean,这里不再直接传递参数,而是将参数的传递交给了 UserService userService = (UserService) beanFactory.getBean("userService"); userService.queryUserInfo(); }
可以发现,获取bean调用的
getBean
操作并没有传递任何参数,也就是调用的底层的无参构造,然后在获取bean的时候,将bean所依赖的一些属性保存到了PropertyValues
中,重点注意第12行,这里userService
的bean依赖于userDao的bean,保存属性时不是直接保存userDao
的bean对象,而是保存了一个BeanReference
的对象,内部存储了userDao
的bean对象的名称,后期属性填充时再转去创建这个依赖的bean属性
总结
经历上面的改造,项目实现了bean的创建与属性填充分开的操作,主要是在AbstractAutowireCapableBeanFactory
类中将这两步操作分开,实现了bean可以依赖另外的bean,并且在使用的时候才新建bean对象