Small Spring IOC篇:实现 Bean 的定义、注册、获取

一、前言

本来是打算背八股来学习Spring的,但是发现很多内容如果不亲自敲一遍根本记不住,同时也没法学习到Spring框架源码中的各种精妙设计,尤其是设计模式的运用,于是打算新开一个项目,通过一步一步的增加功能,最后实现一个简易Spring。这一节先从IOC容器讲起,一步一步的将代码复杂化,一步一步贴近真正的spring框架。

另外也不要想着去默写源码之类的,我把02版本默写了一次,其实就已经有点吃不消了,到后面还会扩展至几十个类,默写肯定是不现实的。这里着重去学架构思维即可。

二、01-最简Bean容器版本

这里我们先给出Bean容器最初的样子,很朴实,就是一个基于Map存储的容器,根据键拿值(对象)。选择Map作为底层容器是因为Map的特性,利用哈希可以通过键查询快速定位值。

java 复制代码
public class BeanDefinition {

    private Object bean;

    public BeanDefinition(Object bean) {
        this.bean = bean;
    }

    public Object getBean() {
        return bean;
    }

}
java 复制代码
public class BeanFactory {

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

    public Object getBean(String name) {
        return beanDefinitionMap.get(name).getBean();
    }

    public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(name, beanDefinition);
    }

}

可以看到这个版本也是基本实现了spring功能的,但是也可以看到部分问题,比如创建对象的步骤是在框架外部进行的,正常我们应该传入一个Class,然后由框架内部帮忙创建Bean对象(反射),所以这都是后面可以进行改进的地方。

java 复制代码
@Test
public void test_BeanFactory(){
    // 1.初始化 BeanFactory
    BeanFactory beanFactory = new BeanFactory();
    
    // 2.注册 bean
    BeanDefinition beanDefinition = new BeanDefinition(new UserService());
    beanFactory.registerBeanDefinition("userService", beanDefinition);
    
    // 3.获取 bean
    UserService userService = (UserService) beanFactory.getBean("userService");
    userService.queryUserInfo();
}

三、02-IOC核心版本

基于上一个版本,我们将对最后提到的问题进行优化,因此这次我先给测试类,看看这次我们实现的效果能做到什么样:

java 复制代码
    @Test
    public void test_BeanFactory() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        // 1.初始化 BeanFactory
        //    注意这里用的是 DefaultListableBeanFactory(那个真正有 registerBeanDefinition 的实现类),
        //    不是 BeanFactory 接口 ------ 接口上并没有注册方法。
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2.注册 bean:这里传的是 UserService.class,不是 new UserService()
        BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
        beanFactory.registerBeanDefinition("userService", beanDefinition);

        // 3.第一次获取 bean:缓存里没有,走 createBean 反射实例化
        UserService userService = (UserService) beanFactory.getBean("userService");
        userService.queryUserInfo();

        // 4.第二次获取 bean from Singleton:这次必须直接命中单例缓存,不能再 new 一个
        UserService userService_singleton = (UserService) beanFactory.getBean("userService");
        userService_singleton.queryUserInfo();

        // 额外验证:两次拿到的是不是同一个对象(这一章真正的考点)
        System.out.println("两次获取是否为同一个对象:" + (userService == userService_singleton));
    }

这里先给出整体架构,可以看到相较于前面的最简版本,这里多出了很多类。但是也不用太害怕,这些类大多数是为了解耦而存在的,所以比起背源码,把这几个核心类的功能记住才是真的。

java 复制代码
small-spring-step-02
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework.beans
    │           ├── factory
    │           │   ├── config
    │           │   │   ├── BeanDefinition.java
    │           │   │   └── SingletonBeanRegistry.java
    │           │   ├── support
    │           │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   ├── AbstractBeanFactory.java
    │           │   │   ├── BeanDefinitionRegistry.java
    │           │   │   ├── DefaultListableBeanFactory.java
    │           │   │   └── DefaultSingletonBeanRegistry.java
    │           │   └── BeanFactory.java
    │           └── BeansException.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   └── UserService.java
                └── ApiTest.java

依赖关系:

1.BeanDefinition

从这里开始,BeanDefinition就不是前面最简版的定义了,前面的它就是一个装载Bean对象的类,把Bean对象作为属性存放。而这个版本,由于我们需求变了,这个类中的属性也就变成了装载Class对象了。

所以这个类顾名思义,就是Bean的定义类。

这里可能会有疑惑,为什么要保存 Class 对象,而不是直接保存 Bean 实例?

因为 Spring 的设计并不是在注册 Bean 时就把所有 Bean 都创建出来,而是先保存 Bean 的定义信息,后续由容器根据这些定义完成 Bean 的创建、管理以及缓存。

在真实 Spring 中,BeanDefinition 后续还会承载更多 Bean 的定义信息;而在当前这个手写 Spring 的第三章里,为了简化实现,暂时只保存 Bean 对应的 Class 类型。

至于我们平时使用的@Component@Service@Controller 等注解,Spring 会在应用启动阶段 进行组件扫描,识别这些注解标记的类,并进一步将它们转换、注册为 BeanDefinition。这里的扫描过程属于运行时的容器启动过程,而不是编译时。这一部分会在后面的章节继续实现。

java 复制代码
public class BeanDefinition {

    private Class beanClass;

    public BeanDefinition(Class beanClass) {
        this.beanClass = beanClass;
    }
		// ...get/set
}

2. 单例注册接口定义和实现

从前面我们可以看出Bean的创建流程:首先先存在BeanDefinition中,然后创建为Bean对象。

我们先来看创建为Bean对象后,这个对象应该存哪里,怎么存?

于是我们专门用了一个类来实现这个功能:DefaultSingletonBeanRegistry(默认单例Bean注册器),这个地方和最简版的BeanFactory干了一样的事情:

java 复制代码
public interface SingletonBeanRegistry {

    Object getSingleton(String beanName);

}
java 复制代码
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {

    private Map<String, Object> singletonObjects = new HashMap<>();

    @Override
    public Object getSingleton(String beanName) {
        return singletonObjects.get(beanName);
    }

    protected void addSingleton(String beanName, Object singletonObject) {
        singletonObjects.put(beanName, singletonObject);
    }

}

3.抽象类定义模板方法

AbstractBeanFactory 模板抽象类是用来规定Bean的获取流程(getBean)骨架的,它提供了两个抽象方法,由子类实现具体功能。学了大营销应该就会很熟悉这个设计模式,当时在策略领域时,我们就用了模板抽象类来定义了抽奖的规则过滤骨架(前置中置)。

同时这个抽象类继承了默认单例注册器类,因此是可以直接控制bean对象的获取和存储的。

在流程骨架中,我们先会尝试去获取单例容器中的Bean,如果没有才会去创建;而创建过程还额外需要BeanDefiniton,因此获取BeanDefinition和createBean我们都交由子类去实现

最终目的还是为了解耦。

java 复制代码
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

    @Override
    public Object getBean(String name) throws BeansException {
        Object bean = getSingleton(name);
        if (bean != null) {
            return bean;
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        return createBean(name, beanDefinition);
    }

    protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;

    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException;

}

(1)createBean

单独交由子类 AbstractAutowireCapableBeanFactory 实现:

java 复制代码
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException {
        Object bean = null;
        try {
            bean = beanDefinition.getBeanClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        addSingleton(beanName, bean);
        return bean;
    }

}

(2)getBeanDefinition

这里面用于存储BeanDefinition,然后实现getBeanDefinition方法。

DefaultListableBeanFactory是最核心的类,为啥呢?

因为他是处于继承链的顶端,也就是可以通过这个类间接调用前面的所有功能。

因此在我们测试类中,是直接通过这一个类就能调用前面定义和实现的所有方法。

java 复制代码
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {

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

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws BeansException {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) throw new BeansException("No bean named '" + beanName + "' is defined");
        return beanDefinition;
    }

}

测试类中所有的方法都是由这个 DefaultListableBeanFactory 对象提供的:

然而这个版本还有缺陷,你可以注意到,我们当前是不支持有参构造的,我们的UserService一直都是无参构造出来的。

java 复制代码
public class UserService {

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

}

一旦增加一些成员变量,要用到有参构造,运行时就会报错:

java 复制代码
public class UserService {

    private String name;

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

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

}

这个问题我们留到下个版本解决。

四、03-Cglib优化版本

这个版本就是为了优化上一节最后留的问题,对于这个优化,我的顺序和xfg略有不同,我倾向于从问题点出发:

根据上面的报错,首先就找到的createBean这个方法,所以一定是在实例化Bean的时候出了问题,

java 复制代码
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException {
        Object bean = null;
        try {
            bean = beanDefinition.getBeanClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        addSingleton(beanName, bean);
        return bean;
    }

}

问题的根源就出自这里:这里默认是进行无参构造,如果只有有参构造是不行的,这里要通过构造器反射再进行实例化。

java 复制代码
bean = beanDefinition.getBeanClass().newInstance();

而对于这里的构造,其实是有两种方式实现的:一种是cglib,一种是JDK。因此这属于一种策略选择,我们使用策略模式进行代码的实现。

1.重载getBean

首先要重载getBean方法,我们需要提供额外的参数用于构造:

java 复制代码
public interface BeanFactory {

    Object getBean(String name) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

}

2.策略模式

然后提供接口,策略模式。

java 复制代码
public interface InstantiationStrategy {

    Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;

}

JDK实现:

java 复制代码
public class SimpleInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {

        Class clazz = beanDefinition.getBeanClass();
        try {
            if (ctor != null) {//如果有有参构造器,就传参进来
                return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);

            } else {//如果没有有参构造器,就默认无参
                return clazz.getDeclaredConstructor().newInstance();
            }
        } catch (Exception e) {
            throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);
        }
    }
}

cglib实现:

java 复制代码
public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(beanDefinition.getBeanClass());
        enhancer.setCallback(new NoOp() {
            @Override
            public int hashCode() {
                return super.hashCode();
            }
        });
        if (null == ctor) return enhancer.create();
        return enhancer.create(ctor.getParameterTypes(), args);
    }

}

3.createBean提供策略选择

java 复制代码
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    private  InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @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;
    }

    protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {
        Constructor constructorToUse = null;
        Class<?> beanClass = beanDefinition.getBeanClass();
        Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
        for (Constructor ctor : declaredConstructors) {
            if (null != args && ctor.getParameterTypes().length == args.length) {
                constructorToUse = ctor;
                break;
            }
        }
        return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);
    }

    public InstantiationStrategy getInstantiationStrategy() {
        return instantiationStrategy;
    }

    public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
        this.instantiationStrategy = instantiationStrategy;
    }
}

4.测试

java 复制代码
public class UserService {

    private String name;

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

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

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("");
        sb.append("").append(name);
        return sb.toString();
    }
}
java 复制代码
public class ApiTest {

    @Test
    public void test_BeanFactory() {
        // 1.初始化 BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2. 注入bean
        BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
        beanFactory.registerBeanDefinition("userService", beanDefinition);

        // 3.获取bean
        UserService userService = (UserService) beanFactory.getBean("userService", "小傅哥");
        userService.queryUserInfo();
    }
    
}
相关推荐
Java_2017_csdn2 小时前
ReentrantLock 与 Redis RLock 对比解读
java
边境悍匪2 小时前
蜗牛学苑 Java 智能体学习 Day45|Knife4j+SpringBoot3、书城项目整合 MyBatis 思维导图复盘
java·开发语言·vue.js·学习·spring
需要8262 小时前
Arthas 一个命令排查线上问题
java·jvm·spring·spring cloud
字节探索3 小时前
别再只会写 Controller 了!一文吃透 Spring Boot 高级特性与项目实战
spring boot·spring
Kyrie_kk3 小时前
Java--ProcessBuilder操作系统进程
java·后端
SamDeepThinking3 小时前
关于java final关键字的可见性
java·后端·面试
Wang's Blog3 小时前
Java 项目实战: 外卖平台-MyBatis-Plus分页插件与员工分页查询
java·项目开发
MetaLite3 小时前
全网最好的SpringBoot接口请求对象设计
java·spring boot·后端
蜗牛互联网3 小时前
AI给面试打分不够,求职者更需要可核对的证据
java·人工智能·后端