手写Spring-第四章-为依赖的对象注入灵魂!

前言

经过了前面的三章,不知道大家有没有一个感觉,总觉得有些怀念Spring中我们最常用到的那个东西,但是等啊等啊,一直没有提到它。没错,就是我们的@Autowired!单丝不成线,孤木不成林。很多时候我们的一个bean,并不能仅靠自己就完成一项任务,它会需要依赖其他的bean。我们常用的做法,自然就是将依赖的bean注入进来。那么这一章,我们就来解决这件事情:如何解决bean的依赖。虽然还没有达到@Autowired这样,加一个注解就解决问题的程度,但我们还是可以通过这一章,来达成bean之间的依赖问题,以及bean对其他普通属性的依赖问题。

工程结构

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─akitsuki
│  │  │          └─springframework
│  │  │              └─beans
│  │  │                  ├─exception
│  │  │                  │      BeanException.java
│  │  │                  │  
│  │  │                  └─factory
│  │  │                      │  BeanFactory.java
│  │  │                      │  
│  │  │                      ├─config
│  │  │                      │      BeanDefinition.java
│  │  │                      │      BeanReference.java
│  │  │                      │      DefaultSingletonBeanRegistry.java
│  │  │                      │      PropertyValue.java
│  │  │                      │      PropertyValues.java
│  │  │                      │      SingletonBeanRegistry.java
│  │  │                      │  
│  │  │                      └─support
│  │  │                              AbstractAutowireCapableBeanFactory.java
│  │  │                              AbstractBeanFactory.java
│  │  │                              BeanDefinitionRegistry.java
│  │  │                              CglibSubclassingInstantiationStrategy.java
│  │  │                              DefaultListableBeanFactory.java
│  │  │                              InstantiationStrategy.java
│  │  │                              SimpleInstantiationStrategy.java
│  │  │                    
│  │  └─resources
│  └─test
│      └─java
│          └─com
│              └─akitsuki
│                  └─springframework
│                      └─test
│                          │  ApiTest.java
│                          │  
│                          └─bean
│                                  UserDao.java
│                                  UserService.java

基本上没有增加什么新的类,这一次的任务不算很难。

包装,包装,把一切都包装起来

我们的bean,需要依赖一些东西,才能完成自己的工作。而依赖的这些东西,实际上都将会作为这个bean的属性。所以,我们需要有一个类来描述这些属性。

package com.akitsuki.springframework.beans.factory.config;

import lombok.Getter;
import lombok.Setter;

/**
 * bean中的property名称和值
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/8 14:19
 */
@Getter
@Setter
public class PropertyValue {

    private String name;

    private Object value;

    public PropertyValue(String name, Object value) {
        this.name = name;
        this.value = value;
    }
}

很好理解,对吧。这个类里面有两个字段,分别代表依赖属性的名称和值。

那么有了上面这个类用来描述属性,我们知道,一个类很多时候不会只有一个依赖,那我们还需要一个类,用来表述这些属性的集合。

package com.akitsuki.springframework.beans.factory.config;

import java.util.HashMap;
import java.util.Map;

/**
 * 用来包装和集合PropertyValue的类
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/8 14:22
 */
public class PropertyValues {

    private Map<String, PropertyValue> propertyValueMap = new HashMap<>();

    public void addPropertyValue(PropertyValue propertyValue) {
        propertyValueMap.put(propertyValue.getName(), propertyValue);
    }

    public PropertyValue getPropertyValue(String name) {
        return propertyValueMap.get(name);
    }

    public PropertyValue[] getPropertyValues() {
        return propertyValueMap.values().toArray(new PropertyValue[0]);
    }
}

那么我们就可以用 PropertyValues来表示,一个bean中的所有依赖属性了。

这个时候,我们遇到了一些特殊的情况。如果我们的依赖,是一个普通的字段,比如一个int类型、Spring类型的字段,那我们自然可以在PropertyValue里面,对这个字段进行包装。但是,如果这个依赖是另一个bean呢?这里的value我们是给不出来的,因为bean的实例化是交给Spring来做的。那么这个时候,我们就需要一个标识,来告诉Spring,某个属性,是需要引用其他的bean的,不能把它当作普通的属性来处理。

package com.akitsuki.springframework.beans.factory.config;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * Bean引用接口,用于指明类中的某个依赖是bean的引用
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/8 14:17
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class BeanReference {
    private String name;
}

于是我们就有了这个类。当我们bean中的依赖是另一个bean的引用的时候,就可以用这个标识来进行指示。具体怎么用,我们下面再来具体解释。

bean定义,迎来了一次扩充

我们之前的工程中,对于bean的定义,只有bean的class信息。这次我们将bean中的依赖属性也作为bean定义中的一员,扩展bean定义的内容。

package com.akitsuki.springframework.beans.factory.config;

import lombok.Getter;
import lombok.Setter;

/**
 * Bean的定义,包含bean的一些基本信息
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 9:54
 */
@Getter
@Setter
public class BeanDefinition {

    /**
     * bean的class
     */
    private final Class<?> beanClass;

    /**
     * bean的属性和值
     */
    private PropertyValues propertyValues = new PropertyValues();

    public BeanDefinition(Class<?> beanClass) {
        this.beanClass = beanClass;
    }

    public BeanDefinition(Class<?> beanClass, PropertyValues propertyValues) {
        this.beanClass = beanClass;
        this.propertyValues = propertyValues;
    }
}

起始之刻,灵魂的注入

开头我们就说,要为依赖的对象注入灵魂。那么,我们在何时注入这个灵魂呢?答案就是在我们创建bean的时候。还记得我们创建bean方法是在哪个类具体实现的吗?对,就是那个名字很奇怪的类:AbstractAutowireCapableBeanFactory

package com.akitsuki.springframework.beans.factory.support;

import cn.hutool.core.bean.BeanUtil;
import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.BeanReference;
import com.akitsuki.springframework.beans.factory.config.PropertyValue;
import com.akitsuki.springframework.beans.factory.config.PropertyValues;

import java.lang.reflect.Constructor;

/**
 * 抽象的实例化Bean类,提供创建Bean的能力,创建完成后,放入单例bean map中进行缓存
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 10:12
 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    /**
     * 实例化策略,在构造方法中进行初始化
     */
    private final InstantiationStrategy strategy;

    protected AbstractAutowireCapableBeanFactory(Class<? extends InstantiationStrategy> clazz) throws InstantiationException, IllegalAccessException {
        strategy = clazz.newInstance();
    }

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeanException {
        Object bean;
        try {
            bean = createBeanInstance(beanDefinition, beanName, args);
            //设置bean属性
            applyPropertyValues(beanName, beanDefinition, bean);
            //创建好的单例bean,放入缓存
            addSingleton(beanName, bean);
        } catch (Exception e) {
            throw new BeanException("创建bean失败", e);
        }
        return bean;
    }

    protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) throws BeanException {
        //拿到所有的构造方法
        Constructor<?>[] declaredConstructors = beanDefinition.getBeanClass().getDeclaredConstructors();
        //开始循环,找到和参数类型一致的方法
        for (Constructor<?> c : declaredConstructors) {
            if (c.getParameterTypes().length == args.length) {
                boolean flag = true;
                for (int i = 0; i < c.getParameterTypes().length; i++) {
                    if (!args[i].getClass().equals(c.getParameterTypes()[i])) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    //调用策略来进行实例化
                    return strategy.instantiate(beanDefinition, beanName, c, args);
                }
            }
        }
        throw new BeanException("创建bean出错,未找到对应构造方法");
    }

    /**
     * 为生成的bean设置属性
     *
     * @param beanName       bean的名称
     * @param beanDefinition bean的定义
     * @param bean           等待设置属性的bean
     */
    protected void applyPropertyValues(String beanName, BeanDefinition beanDefinition, Object bean) throws BeanException {
        PropertyValues propertyValues = beanDefinition.getPropertyValues();
        for (PropertyValue pv : propertyValues.getPropertyValues()) {
            String name = pv.getName();
            Object value = pv.getValue();
            if (value instanceof BeanReference) {
                try {
                    value = getBean(((BeanReference) value).getName());
                } catch (Exception e) {
                    throw new BeanException("设置bean属性时异常:" + beanName, e);
                }
            }
            BeanUtil.setFieldValue(bean, name, value);
        }
    }
}

好长好长,我们来一点点分析。

首先自然是看我们的 createBean方法。可以看到,我们加了一个步骤:设置bean属性。那么我们的流程就变成了:创建bean实例->填充依赖属性->放入缓存->返回。那么我们就来看看这个新增的【设置bean属性】步骤。如果刨开中间对 BeanReference的这一大段不看,其实很好理解,就是调用工具类,将属性和值按照名称设置进刚才创建好的bean实例中。顺带一提,这里使用的 BeanUtil,是 hutool工具包下的一个工具类,可以很方便的操作对象。

那么我们对值是 BeanReference的属性是怎么操作的?通过上面我们知道,这样的属性,代表这是一个bean的引用。所以我们需要将它的值设为一个bean。那么这个bean从哪里来呢?当然是调用getBean方法了。这个时候就会有2种情况:

  1. 这个bean已经创建好了。那直接从缓存中返回就可以了
  2. 这个bean还没有被创建。不用担心,getBean方法里面对没有创建的bean会自动进行创建

这样我们就解决了依赖bean的问题,实现了灵魂的注入。

看到这里,可能会联想到上次的有参构造方法。我们一般会在构造方法中,将对象的一些属性进行初始化。那么这一次,我们实际上解决了不通过构造方法对属性进行初始化的问题,也是我们平时@Autowired所习惯的。

对⭐羁绊⭐进行测试吧!

经过我们的不懈努力,我们终于可以让bean之间可以产生羁绊(依赖)了!那么事不宜迟,赶紧为我们之前孤零零的bean,找一位可以产生羁绊的好伙伴。

package com.akitsuki.springframework.test.bean;

import java.util.HashMap;
import java.util.Map;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/11/8 14:42
 */
public class UserDao {

    private static final Map<Long, String> userMap = new HashMap<>();

    static {
        userMap.put(1L, "akitsuki");
        userMap.put(2L, "toyosaki");
        userMap.put(3L, "kugimiya");
        userMap.put(4L, "hanazawa");
        userMap.put(5L, "momonogi");
    }

    public String queryUserName(Long id) {
        return userMap.get(id);
    }
}

我们之前的bean,命名为UserService。我们知道,一般都会由Service层去调用Dao层,Dao层去操作数据库来获取数据。所以很自然的,我们就有了一个UserDao,作为被依赖的对象。这里为了简化操作,就使用静态数据了。那么,UserService,快点依赖起来罢!

package com.akitsuki.springframework.test.bean;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/11/8 14:42
 */
public class UserService {

    private String dummyString;

    private int dummyInt;

    private UserDao userDao;

    public void queryUserInfo(Long id) {
        System.out.println("dummyString:" + dummyString);
        System.out.println("dummyInt:" + dummyInt);
        String userName = userDao.queryUserName(id);
        if (null == userName) {
            System.out.println("用户未找到>_<");
        } else {
            System.out.println("用户名:" + userDao.queryUserName(id));
        }
    }
}

终于,它看起来像一个正常的Service了。还添加了两个普通属性作为添头,测试一下普通属性的注入。

我们正式开始测试

package com.akitsuki.springframework.test;

import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.BeanReference;
import com.akitsuki.springframework.beans.factory.config.PropertyValue;
import com.akitsuki.springframework.beans.factory.config.PropertyValues;
import com.akitsuki.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy;
import com.akitsuki.springframework.beans.factory.support.DefaultListableBeanFactory;
import com.akitsuki.springframework.test.bean.UserDao;
import com.akitsuki.springframework.test.bean.UserService;
import org.junit.Test;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/11/8 14:42
 */
public class ApiTest {

    @Test
    public void test() throws Exception {
        //初始化bean工厂
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(CglibSubclassingInstantiationStrategy.class);
        //初始化Bean UseDao
        BeanDefinition beanDefinition = new BeanDefinition(UserDao.class);
        beanFactory.registerBeanDefinition("userDao", beanDefinition);
        //初始化Bean UserService
        PropertyValue dummyString = new PropertyValue("dummyString", "dummy");
        PropertyValue dummyInt = new PropertyValue("dummyInt", 114514);
        PropertyValue userDao = new PropertyValue("userDao", new BeanReference("userDao"));
        PropertyValues propertyValues = new PropertyValues();
        propertyValues.addPropertyValue(dummyInt);
        propertyValues.addPropertyValue(dummyString);
        propertyValues.addPropertyValue(userDao);
        BeanDefinition beanDefinition1 = new BeanDefinition(UserService.class, propertyValues);
        beanFactory.registerBeanDefinition("userService", beanDefinition1);
        //获取UserService并使用
        UserService userService = (UserService) beanFactory.getBean("userService");
        userService.queryUserInfo(1L);
        System.out.println("---------");
        userService.queryUserInfo(5L);
        System.out.println("---------");
        userService.queryUserInfo(114514L);
    }
}

测试结果

dummyString:dummy
dummyInt:114514
用户名:akitsuki
---------
dummyString:dummy
dummyInt:114514
用户名:momonogi
---------
dummyString:dummy
dummyInt:114514
用户未找到>_<

Process finished with exit code 0

看起来很长,实际上大部分的内容都用在了BeanDefinition的制作上。功夫不负有心人,我们的bean依赖成功的被注入进来了。普通属性也正常地进行了注入。

不过这里也体现出一个问题,那就是,我们对BeanDefinition的构建操作,太过于繁琐了。想想我们正常使用Spring的时候,写一写配置文件,加一加注解,Spring自动就帮我们注册好了。那么下一章,我们就来填上这个大坑,让配置文件来帮我们完成Bean的注册!

相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring,这里对应的代码是mini-spring-04

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值