前言
上次我们终于把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