手写Spring-第三章-来填坑吧!有参bean的实例化策略

前言

上次我们终于把bean实例化这一步骤交给Spring容器了,这是个了不起的进步。但是,我们还是埋下了一个坑,那就是,这个实例化的手段,过于粗糙了。我们仅仅是简单的调用了newInstance方法而已,这样只能新建一个无参对象。当我们遇到有参构造方法的时候,这里就会报错了。那么这一章,我们就来填上这个坑,让有参bean也可以正常实例化!

工程结构

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─akitsuki
│  │  │          └─springframework
│  │  │              └─beans
│  │  │                  ├─exception
│  │  │                  │      BeanException.java
│  │  │                  │  
│  │  │                  └─factory
│  │  │                      │  BeanFactory.java
│  │  │                      │  
│  │  │                      ├─config
│  │  │                      │      BeanDefinition.java
│  │  │                      │      DefaultSingletonBeanRegistry.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
│                                  UserService.java

经过了上一章的折腾,这一次的结构看起来好像就没那么吓人了。嗯,让我们轻松写意地完成这一章吧。

工厂的原材料变多了!

还记得我们的BeanFactory接口吗?那是一个给出bean名称,就可以获得一个活蹦乱跳的bean的神奇工厂。但是这回可不行了,只有bean名称,已经不足以描述出我们需要的bean了。这个bean需要一些参数用来初始化!

package com.akitsuki.springframework.beans.factory;

import com.akitsuki.springframework.beans.exception.BeanException;

/**
 * bean工厂接口,用来获取bean
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 10:03
 */
public interface BeanFactory {

    /**
     * 获取Bean(有参)
     *
     * @param beanName bean的名称
     * @param args     参数
     * @return bean
     * @throws BeanException exception
     */
    Object getBean(String beanName, Object... args) throws BeanException;
}

工厂升级了制造工艺,原材料的选择变得多种多样了!我们自然可以像原来那样,只给一个名称,就从工厂获取bean。而对于需要参数才能初始化的bean,我们的工厂现在也可以进行生产加工了。

工厂整顿,改进制造工艺!

说起来,上面的BeanFactory接口,只能是工厂领导的指导意见,真正实施起来,还得我们下面具体干活的人来进行改进。首当其冲的就是我们接口的直接实现类:AbstractBeanFactory。

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

import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.BeanFactory;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.DefaultSingletonBeanRegistry;

/**
 * 抽象bean工厂,实现了BeanFactory,提供获取Bean的实现
 * 在获取Bean的方法中,调用了两个抽象方法:获取定义、创建Bean
 * 这两个抽象方法由子类来实现,这里只定义过程
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 10:04
 */
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

    @Override
    public Object getBean(String beanName, Object... args) throws BeanException {
        //这里先尝试获取单例Bean,如果可以获取到就直接返回
        Object bean = getSingleton(beanName);
        if (null != bean) {
            return bean;
        }
        //这里是上面获取单例Bean失败的情况,需要先调用抽象的获取bean定义的方法,拿到bean的定义,然后再通过这个来新创建一个Bean
        BeanDefinition beanDefinition = getBeanDefinition(beanName);
        return createBean(beanName, beanDefinition, args);
    }


    /**
     * 获取Bean的定义
     *
     * @param beanName bean的名字
     * @return beanDefinition
     * @throws BeanException exception
     */
    protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeanException;

    /**
     * 创建Bean
     *
     * @param beanName       bean的名字
     * @param beanDefinition bean的定义
     * @param args           构造方法参数
     * @return bean
     * @throws BeanException exception
     */
    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeanException;
}

让我们来看看这个类是如何改进生产工艺的。可以看到,只做了一些微调,就完成了领导的要求。在getBean方法中,大部分内容是不需要动的,也就是直接从缓存中获取单例bean这一部分。因为我们的参数,只针对于bean初始化过程中才有用,当bean初始化完成后,就不需要了。相对的,创建bean的方法则需要进行改造,我们在原来的基础上,增加了初始化参数的传递。

bean的生产策略!

上面的这个类,说是下面的人,但是在我们的工厂里面,也是个二把手。具体怎么将参数用到bean的生产中,AbstractBeanFactory还是没有给出答案,我们还得继续往下深究。

就算是知道要生产有参的bean,我们还是可以用多种方式去生产。拿这次的例子来说,我们既可以用JDK自带的反射来实现,也可以用Cglib来实现。而这两种实现方式,我们都可以抽象成策略。定义一个接口,来描述这种策略,由具体的类来实现这个策略。将来如果还有其他的实现方式,我们依然可以通过新增一个实现类的方式,来轻松扩展。这里用到的,就是设计模式中的策略模式

那么对于我们的生产策略来说,必要的东西是什么呢?bean定义、bean名称、用到的构造方法、相应的参数,有了这四个内容,我们就可以来定义生产策略了。

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

import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;

/**
 * 实例化Bean策略接口
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 13:50
 */
public interface InstantiationStrategy {

    /**
     * 实例化bean方法
     *
     * @param beanDefinition bean定义
     * @param beanName       bean名称
     * @param constructor    构造方法
     * @param args           构造方法入参
     * @return bean
     * @throws BeanException exception
     */
    Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor<?> constructor, Object[] args) throws BeanException;
}

有了策略,就要有具体的实现。上面我们也说了两种实现,一种是用普通的反射,一种则是用Cglib。下面我们分别来介绍。

首先是反射的实现方式

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

import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 普通实例化bean策略实现类
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 13:52
 */
public class SimpleInstantiationStrategy implements InstantiationStrategy {
    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor<?> constructor, Object[] args) throws BeanException {
        Class<?> clazz = beanDefinition.getBeanClass();
        try {
            if (null != constructor) {
                return constructor.newInstance(args);
            } else {
                return clazz.getDeclaredConstructor().newInstance();
            }
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new BeanException("实例化Bean失败", e);
        }
    }
}

首先自然还是从Bean定义中拿到class,然后判断构造方法是否为空。如果不为空,则调用newInstance的有参方法,将参数传入即可。如果为空,则从class中拿到无参的构造方法 ,再调用newInstance的无参方法即可。

然后是Cglib的实现方式

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

import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

import java.lang.reflect.Constructor;

/**
 * cglib实例化bean策略实现类
 *
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 13:57
 */
public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor<?> constructor, Object[] args) throws BeanException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(beanDefinition.getBeanClass());
        enhancer.setCallback(new NoOp() {

        });
        if (null == constructor) {
            return enhancer.create();
        } else {
            return enhancer.create(constructor.getParameterTypes(), args);
        }
    }
}

这一套可以说是固定的一个写法,这里也不做过多解释,具体的实现步骤其实不是很重要,重要的是构造出与上面不同的一种bean实例化策略。感兴趣可以去搜索学习一下Cglib相关内容。

终于找到了最终实现者!

我们的工厂改造计划,绕了那么久,从大领导(BeanFactory)发布命令,到中层领导(AbstractBeanFactory)规划路线,全场员工一起制定了策略(InstantiationStrategy),那么最终这个生产的活到底要落到谁的手头上干呢?嗯,回想一下上一章,是谁最终实现createBean方法的…很好,找到正主了。

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

import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;

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,放入缓存
            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出错,未找到对应构造方法");
    }
}

哎呀,看来这个活真是不好干。类的内容一下子比上次多出来许多。我们一点点来分析。

首当其冲的是多了个属性,我们的生产策略,在类初始化的时候传进来,决定具体用哪种策略。这个也很好理解。

接下来我们来看实现的重头戏:createBeanInstance方法。在这个方法中,我们完成了一个最重要的事情,那就是找到最终要用的构造方法。在前面写那么多内容的时候,不知道你有没有一个感觉:这个Constructor参数,我要从哪里搞来?答案就在这里。我们通过传入的参数,循环判断应该匹配到哪一个构造方法,进而获取到这个Constructor参数。最终,交给策略来进行实例化。

是不是忘了什么?

到这儿,好像我们要填的坑已经基本完成了,但还有那么一点点。我们之前的那个集万千宠爱于一身的类:DefaultListableBeanFactory。由于它继承了AbstractAutowireCapableBeanFactory,所以它也要增加一个构造方法,来满足父类的有参构造方法。

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {

    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    public DefaultListableBeanFactory(Class<? extends InstantiationStrategy> clazz) throws InstantiationException, IllegalAccessException {
        super(clazz);
    }
}

其他部分则不需要进行改造。

来吧,新工厂生产线要开始测试了!

首先,我们需要一个带参数的bean

package com.akitsuki.springframework.test.bean;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 13:44
 */
public class UserService {

    private String info;

    public UserService() {
        info = "default";
    }

    public UserService(String info) {
        this.info = info;
    }

    public void queryUserInfo() {
        System.out.println("查询用户信息:" + info);
    }
}

然后,我们就开始开动机器,测试生产线!

package com.akitsuki.springframework.test;

import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy;
import com.akitsuki.springframework.beans.factory.support.DefaultListableBeanFactory;
import com.akitsuki.springframework.beans.factory.support.SimpleInstantiationStrategy;
import com.akitsuki.springframework.test.bean.UserService;
import org.junit.Test;

/**
 * @author ziling.wang@hand-china.com
 * @date 2022/11/7 13:45
 */
public class ApiTest {

    @Test
    public void test() throws InstantiationException, IllegalAccessException, BeanException {
        //初始化BeanFactory(普通方式)
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(SimpleInstantiationStrategy.class);
        //注入bean
        BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
        beanFactory.registerBeanDefinition("userService", beanDefinition);
        //获取bean
        UserService userService = (UserService) beanFactory.getBean("userService", "akitsuki");
        userService.queryUserInfo();
        //获取bean(传入其他参数,这时应该从缓存中获取,参数无效)
        userService = (UserService) beanFactory.getBean("userService", "kouzou");
        //调用bean
        userService.queryUserInfo();
        //注入其他bean
        beanFactory.registerBeanDefinition("userService2", beanDefinition);
        //获取bean(无参,这里应该会是default)
        userService = (UserService) beanFactory.getBean("userService2");
        //调用bean
        userService.queryUserInfo();
        //初始化BeanFactory(cglib方式)
        beanFactory = new DefaultListableBeanFactory(CglibSubclassingInstantiationStrategy.class);
        //注入bean
        beanFactory.registerBeanDefinition("userService", beanDefinition);
        //获取bean
        userService = (UserService) beanFactory.getBean("userService", "kouzou");
        //调用bean
        userService.queryUserInfo();
    }
}

测试结果:

查询用户信息:akitsuki
查询用户信息:akitsuki
查询用户信息:default
查询用户信息:kouzou

Process finished with exit code 0

看起来很长,只不过是因为这里进行了多次测试,换了各种花样而已。实际上真正核心的步骤,和上次的测试是一样的。可以看到,我们的新工厂生产流程,成功的满足了有参bean的生产工作。那么我们这次的填坑之旅,到这里也算是告一段落了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值