手撸Spring学习笔记-4

182 阅读4分钟

6fdeffd3e4a84e577d9480f632ca32d5.jpeg

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

小付哥手撸Spring学习笔记

作者:小米粥

专栏地址:手撸Spring专栏

一、前言

说书唱戏劝人方,三条大陆走中央。善恶到头终有报,人间正道是沧桑。

您的假期余额已不足,清明小假期,sou~的一下就没有了。

持之以恒的去做一件事,真的是很困难的,昨天我就没有进行更新,偷了一次懒。确实是自己的原因,再想想那些持续输出爆表的博主们,真的觉得他们很不容易。我从小到现在,就是恒心不够,做事总是三分钟热度。但是这次我想挑战一下我的软肋,继续坚持更文。首次撰写技术性文章,有不足之处,请大家多多谅解。

二、正文

书接上回,上一章节我们埋下了一个坑,是一个关于Bean对象在含有构造参数进行实例化的坑。

我们将实例化对象交给容器进行统一处理,但是在实例化的时候并没有处理对象类有入参的情况。我们根据上次的测试方法,把UserService添加一个含入参信息的构造函数,进行测试:

public class UserService {
​
    private String name;
​
    public UserService(String name) {
        this.name = name;
    }
​
    public void queryUserInfo() {
        System.out.println("查询用户信息...");
    }
}

报错如下:

Caused by: java.lang.InstantiationException: cn.xiaomizhou.springframework.test.bean.UserService
  at java.lang.Class.newInstance(Class.java:427)
  at cn.xiaomizhou.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:28)
  ... 24 more

发生这一现象的主要原因是通过BeanDefinition的class对象去进行实例化的时候,没有考虑构造参数入参的情况。

  1. 我们先在BeanFactory中添加获取Bean时将构造参数入参信息传递的接口。

    public interface BeanFactory {
    ​
        /**
         * 根据name获取bean对象的接口
         * @param name
         * @return
         * @throws BeansException
         */
        Object getBean(String name) throws BeansException;
    ​
    ​
        Object getBean(String name, Object... args) throws BeansException;
    ​
    }
    
  2. Spring一共提供了两种创建含有构造函数Bean对象的方法,实际上使用的就是动态代理,一个jdk默认的动态代理,一个是cglib的动态代理。我们先定义一个实例化的策略接口。

    public interface InstantiationStrategy {
    ​
        /**
         * 实例化策略接口
         *
         * @param beanDefinition bean的定义
         * @param beanName bean的名称
         * @param ctor 里面包含了一些必要类信息,为了拿到符合入参信息相对应的构造函数
         * @param args 入参列表
         * @return
         * @throws BeansException
         */
        Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;
    ​
    }
    
  3. 然后我们使用JDK默认的动态代理,进行实例化策略的具体实现。通过入参信息Constructor判断是否传递参数,然后进行不同的实例化。

    public class SimpleInstantiationStrategy implements InstantiationStrategy {
    ​
        /**
         * JDK实例化
         *
         * @param beanDefinition bean的定义
         * @param beanName       bean的名称
         * @param ctor           里面包含了一些必要类信息,为了拿到符合入参信息相对应的构造函数
         * @param args           入参列表
         * @return
         * @throws BeansException
         */
        @Override
        public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
            Class clazz = beanDefinition.getBeanClass();
    ​
            try {
                if (null != ctor) {
                    //Constructor不为空,代表是有构造参数实例化
                    return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
                }else {
                    //Constructor为空,无构造参数实例化
                    return clazz.getDeclaredConstructor().newInstance();
                }
            } catch (InstantiationException | IllegalAccessException
                    | InvocationTargetException | NoSuchMethodException e) {
                throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);
            }
        }
    }
    
  4. 我们此次只实现JDK默认的方式,下面我们对实例化Bean的流程进行修改,通过获取Bean的Class对象,然后在获取Bean的构造函数传参,判断Bean的构造函数有没有传参,然后进行实例化。

    public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {
    ​
    ​
        private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy();
    ​
        /**
         * 根据Bean Name 和 Bean 定义类创建Bean对象
         * <p>
         *     1.通过 BeanDefinition 获取 BeanClass 对象去创建 Bean 对象
         *     2.加入到 Bean 对象单例容器中
         *     3.返回 Bean 对象
         * </p>
         * @param beanName
         * @param beanDefinition
         * @return
         * @throws BeansException
         */
        @Override
        protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
            Object bean = null;
            try {
                bean = createBeanInstance(beanDefinition, beanName, args);
            } catch (Exception e) {
                throw new BeansException("Instantiation of bean failed", e);
            }
    ​
            addSingleton(beanName, bean);
            return bean;
        }
    ​
    ​
        /**
         * 通过实例化进行创建Bean
         *
         * @param beanDefinition
         * @param beanName
         * @param args
         * @return
         */
        protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {
            Constructor constructorToUse = null;
    ​
            /**
             * <P>
             *      接下来就需要循环比对出构造函数集合与入参信息 args 的匹配情况,这里我们
             *      对比的方式比较简单,只是一个数量对比,而实际 Spring 源码中还需要比对入参
             *      类型,否则相同数量不同入参类型的情况,就会抛异常了
             * </P>
             */
            Class beanClass = beanDefinition.getBeanClass();
            Constructor[] declaredConstructors = beanClass.getDeclaredConstructors();
            for (Constructor constructor : declaredConstructors) {
                if (null != args && constructor.getParameterTypes().length == args.length) {
                    constructorToUse = constructor;
                    break;
                }
            }
    ​
            return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);
        }
    ​
        public InstantiationStrategy getInstantiationStrategy() {
            return instantiationStrategy;
        }
    }
    
  5. 最后,我们通过开头的那个UserService进行测试,加入了一个name的入参。测试方法如下:

    public class ApiTest {
    ​
        @Test
        public void test_factory() {
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    ​
            BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
            factory.registryBeanDefinition("userService", beanDefinition);
    ​
            UserService userService = (UserService) factory.getBean("userService", "小米粥");
            userService.queryUserInfo();
        }
    }
    

    测试结果:

    查询用户信息:小米粥
    ​
    Process finished with exit code 0
    

这就是第三天更文挑战的内容了,预知后事如何,且听下回分解。