手写Spring-第三章-来填坑吧!有参bean的实例化策略
迪丽瓦拉
2024-02-13 11:26:48
0

前言

上次我们终于把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 {@Overridepublic Object getBean(String beanName, Object... args) throws BeanException {//这里先尝试获取单例Bean,如果可以获取到就直接返回Object bean = getSingleton(beanName);if (null != bean) {return bean;}//这里是上面获取单例Bean失败的情况,需要先调用抽象的获取bean定义的方法,拿到bean的定义,然后再通过这个来新创建一个BeanBeanDefinition 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 {@Overridepublic 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 {@Overridepublic 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 clazz) throws InstantiationException, IllegalAccessException {strategy = clazz.newInstance();}@Overrideprotected 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 beanDefinitionMap = new HashMap<>();public DefaultListableBeanFactory(Class 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 {@Testpublic void test() throws InstantiationException, IllegalAccessException, BeanException {//初始化BeanFactory(普通方式)DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(SimpleInstantiationStrategy.class);//注入beanBeanDefinition beanDefinition = new BeanDefinition(UserService.class);beanFactory.registerBeanDefinition("userService", beanDefinition);//获取beanUserService userService = (UserService) beanFactory.getBean("userService", "akitsuki");userService.queryUserInfo();//获取bean(传入其他参数,这时应该从缓存中获取,参数无效)userService = (UserService) beanFactory.getBean("userService", "kouzou");//调用beanuserService.queryUserInfo();//注入其他beanbeanFactory.registerBeanDefinition("userService2", beanDefinition);//获取bean(无参,这里应该会是default)userService = (UserService) beanFactory.getBean("userService2");//调用beanuserService.queryUserInfo();//初始化BeanFactory(cglib方式)beanFactory = new DefaultListableBeanFactory(CglibSubclassingInstantiationStrategy.class);//注入beanbeanFactory.registerBeanDefinition("userService", beanDefinition);//获取beanuserService = (UserService) beanFactory.getBean("userService", "kouzou");//调用beanuserService.queryUserInfo();}
}

测试结果:

查询用户信息:akitsuki
查询用户信息:akitsuki
查询用户信息:default
查询用户信息:kouzouProcess finished with exit code 0

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

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

相关内容