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框架的流程,具体如下图所示:

image-20231031164604160

其实这种依赖关系可以进行配置,也就是这种依赖的属性列表可以配置到xml文件中,然后从xml配置文件中读取到对应的PropertyValues

​ 上一节中我们创建bean的时候,使用getBean函数需要传递bean需要的参数,这样才能创建带参的bean对象,这一节中我们不传递任何参数,只是将需要bean的参数的信息保存到一个类中,先创建空的bean对象,然后再进行属性填充,新的项目结构为:

image-20231031165753636

不再是创建bean时就填充属性,而是创建好了之后再填充属性,这样就不用在getBean时就传递已经实例化的bean属性了,也就是getBean("userService")时不需要实例化userDao的bean对象,只有内部真正需要的时候才创建

在注册bean的时候,不仅保存其类信息,还保存其所有依赖的属性,便于后期属性注入

思路

​ 为了将bean的定义和属性的填充分开,本章中对createBean的方法进一步进行了增强,在上一节中为了实现带参bean的创建,在createBean方法中引入了createBeanInstance方法,根据传递来的参数列表匹配到对应的构造函数从而创建bean对象,本节中进一步对createBean方法进行增强,先调用createBeanInstance方法创建一个空的bean对象,然后利用bean注册时保存的propertyValues来进行属性填充,改动的地方如下图蓝色区域所示:

img

​ 也就是先创建空bean,然后根据属性依赖关系再进行属性填充,为了实现属性填充,本节中又添加了三个新的类,并且在原来项目的基础上对一些类进行了修改,下面具体描述哪些类做了修改,并且新增了哪些类

类的说明

  1. BeanReference:如果某一个bean依赖另外一个bean对象,那么在记录这个bean的所有属性时,被依赖的bean会以这个类的对象出现,内部存储的是这个被依赖的bean的名称,这个类主要起到的作用是记录bean之间的依赖关系,当一个bean被依赖时,不会立马创建bean对象,类的结构为:

    image-20231031183810861

  2. PropertyValue:这个类中保存了当前bean中的某一个参数的信息,包括参数的名称,参数的值,也就是这个类中有两个属性,类的结构如下:

    image-20231031183930906

    当当前bean的属性是普通属性时,name保存这个属性的名称,value保存这个属性的值,当当前bean的属性是另外一个bean时,例如service中的bean依赖与dao中的bean。那么name保存这个属性的名称,value保存的不再是dao中bean对象,而是保存一个BeanReference对象,内部存储了dao中bean对象的名称,相当于name指明这个属性的名称,BeanReference内部指明这个属性依赖的是哪个bean,这样做的目的是为了减缓bean的创建时机

  3. PropertyValues:这个类保存了当前bean的所有属性,每一个属性都保存在一个PropertyValue类中,将所有的属性保存到一个List<PropertyValue>中,在注册bean的时候,不再是单单保存bean的类信息,连同这个容器也一起注册到BeanDefinition中,其中保存了当前这个bean的所有属性,这也说明BeanDefinition这个类要做出修改,之后在属性填充时,会将其一个一个的遍历并进行属性的填充

  4. BeanDefinition:对这个类做出修改,不再是只保存当前这个bean的类信息,还保存当前这个类的所有属性信息,保存到一个propertyValues对象中的List<PropertyValue>中,新的类结构如下:

    image-20231031184527965

  5. AbstractAutowireCapableBeanFactory:上一节中介绍在这个类中实现创建带参bean对象的功能,但是本节中将带参bean对象的创建分为两步,第一步是创建空的bean,第二步是属性填充applyPropertyValues,这个方法是新增的,相当于不再在第一步就创建带参对象,新形成的类结构为:

    image-20231031190103985

    内部利用bean注册时保存的属性列表PropertyValues来逐一填充,下面进行测试,详细代码在 仓库

    对于第一步来说,debug的结果如下,可以看出刚创建好的bean还没有属性填充进去,并且传递的参数args也是空:

    image-20231031205025978

    对于第二步来说,debug的结果如下,普通属性直接填充,bean属性先创建再填充,这里判断是否是bean属性的依据是当前属性是否是一个BeanReference类型的变量:

    image-20231031205502439

​ 经历上面的改造,整个项目的框架就搭好了,那么项目中是如何具体实现bean的实例化和属性填充分开的呢,我们接下来介绍

bean的创建和获取

​ 在上一节中,我们在获取bean的时候,将bean的参数也传递了进去,也就是getBean的时候,同时传递参数,本节中对此进行以下几点改变:

  1. getBean不再传递参数,而是将参数保存到PropertyValues类中

  2. bean实例化时一直调用无参构造,使得bean创建之后,属性都为空

  3. 调用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,再属性填充的操作,具体的流程如下图,可以看到多了一步属性填充的操作:

    image-20231103085923756

    ​ 上面描述了创建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对象