前言

上一章我们实现了一个简化的Bean容器,那么这一章开始,我们就要上一些强度了。这一章主要的目的是,让Spring容器来自动化实现Bean的创建,并且实现单例Bean的复用。在上一章,我们只实现了一个容器,bean的创建,实际上还是我们在注册bean的时候手动new出来的。那么这次,我们就让Spring容器来帮我们做这些工作。

工程结构

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─akitsuki
│  │  │          └─springframework
│  │  │              └─beans
│  │  │                  └─factory
│  │  │                      │  BeanFactory.java
│  │  │                      │
│  │  │                      ├─config
│  │  │                      │      BeanDefinition.java
│  │  │                      │      DefaultSingletonBeanRegistry.java
│  │  │                      │      SingletonBeanRegistry.java
│  │  │                      │
│  │  │                      ├─exception
│  │  │                      │      BeanException.java
│  │  │                      │
│  │  │                      └─support
│  │  │                              AbstractAutowireCapableBeanFactory.java
│  │  │                              AbstractBeanFactory.java
│  │  │                              BeanDefinitionRegistry.java
│  │  │                              DefaultListableBeanFactory.java
│  │  │
│  │  └─resources
│  └─test
│      └─java
│          └─com
│              └─akitsuki
│                  └─springframework
│                      └─test
│                          │  ApiTest.java
│                          │
│                          └─bean
│                                  UserService.java

光是看工程结构就知道,这次强度比起上次来,要暴涨了!(笑)

一切的开始:竟然是异常?

没有异常的程序,是不存在的。只要是程序,就一定会有不按套路出牌的时候。那对于我们的小Spring框架来说,我们也需要一个属于自己的异常类,这样能够帮助我们更好的对异常进行包装。

package com.akitsuki.springframework.beans.factory.exception;/*** Bean相关的异常** @author ziling.wang@hand-china.com* @date 2022/11/7 10:14*/
public class BeanException extends RuntimeException {public BeanException(String info) {super(info);}public BeanException(String info, Exception e) {super(info, e);}
}

嗯,一个很简单的运行时异常,但它是属于我们框架自己的异常,所以还是要给予鼓励~

Bean定义的变化

package com.akitsuki.springframework.beans.factory.config;/*** Bean的定义,将Bean的Class类型注册到这里来** @author ziling.wang@hand-china.com* @date 2022/11/7 9:54*/
public class BeanDefinition {private final Class<?> beanClass;public BeanDefinition(Class<?> beanClass) {this.beanClass = beanClass;}public Class<?> getBeanClass() {return beanClass;}
}

我们可以看到,BeanDefinition不再保存实例化完成的bean了,而是保存了bean的class。这也让它更加符合【bean定义】这个名称。这样,我们就可以将bean的实例化,放在容器中进行,而不是在创建bean定义时就进行。

单例bean注册接口

package com.akitsuki.springframework.beans.factory.config;/*** 单例Bean注册接口** @author ziling.wang@hand-china.com* @date 2022/11/7 9:56*/
public interface SingletonBeanRegistry {/*** 获取单例Bean** @param beanName bean的名称* @return 单例bean*/Object getSingleton(String beanName);/*** 添加一个单例Bean** @param beanName bean的名称* @param bean     单例bean*/void addSingleton(String beanName, Object bean);
}

接下来是一个新玩意儿了。我们知道,接口一般是用来规定一个类应该具有哪些功能的。那么这个接口:单例bean注册接口,自然就是用来规定一个类如果要提供单例bean注册功能,需要提供哪些功能的。这里,有2个方法:获取单例bean添加单例bean,都很好理解。

我们知道,Spring中的bean,在默认情况下和大多数情况下,都是单例的。所以,我们需要一个功能,用来添加和获取这些单例bean,这就是这个接口存在的意义。

单例bean注册接口的默认实现

package com.akitsuki.springframework.beans.factory.config;import java.util.HashMap;
import java.util.Map;/*** 默认的单例Bean注册获取实现** @author ziling.wang@hand-china.com* @date 2022/11/7 9:58*/
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {private final Map<String, Object> singletonBeans = new HashMap<>();@Overridepublic Object getSingleton(String beanName) {return singletonBeans.get(beanName);}@Overridepublic void addSingleton(String beanName, Object singletonBean) {singletonBeans.put(beanName, singletonBean);}
}

有接口,自然就会有接口对应的实现类。可以看到,这个类用一个map来保存实例化好的单例bean,key是bean的名称。不由得让人想到上次的factory中,用来保存bean定义的那个map。这里也是相同的一种做法。方法的实现本身很浅显易懂,这里就不过多解释了

BeanFactory的变化

在上一次,我们的BeanFactory是一个具体的类。但实际上,我们的BeanFactory应该是各种各样的,不止一种实现。所以,在这一次,我们将BeanFactory变成了一个接口。对于所有的BeanFactory来说,它们都应该拥有的一个功能,自然是获取bean。所以我们在BeanFactory这个接口中,就定义这么一个功能,具体的内容,则由实现了这个接口的子类来完成。

package com.akitsuki.springframework.beans.factory;import com.akitsuki.springframework.beans.factory.exception.BeanException;/*** bean工厂接口,用来获取bean** @author ziling.wang@hand-china.com* @date 2022/11/7 10:03*/
public interface BeanFactory {/*** 获取Bean** @param beanName bean的名称* @return bean* @throws BeanException exception*/Object getBean(String beanName) throws BeanException;
}

抽象bean工厂!

这一部分是比较重要的一块。我们先上代码

package com.akitsuki.springframework.beans.factory.support;import com.akitsuki.springframework.beans.factory.BeanFactory;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.DefaultSingletonBeanRegistry;
import com.akitsuki.springframework.beans.factory.exception.BeanException;/*** 抽象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) throws BeanException {//这里先尝试获取单例Bean,如果可以获取到就直接返回Object bean = getSingleton(beanName);if (null != bean) {return bean;}//这里是上面获取单例Bean失败的情况,需要先调用抽象的获取bean定义的方法,拿到bean的定义,然后再通过这个来新创建一个BeanBeanDefinition beanDefinition = getBeanDefinition(beanName);return createBean(beanName, beanDefinition);}/*** 获取Bean的定义** @param beanName bean的名字* @return beanDefinition* @throws BeanException exception*/protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeanException;/*** 创建Bean** @param beanName       bean的名字* @param beanDefinition bean的定义* @return bean* @throws BeanException exception*/protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeanException;
}

有点复杂,我们一点点来分析。

首先,我们可以看到,这是一个抽象类,它继承了上面默认的单例bean注册接口实现类 DefaultSingletonBeanRegistry实现BeanFactory接口。那么这个类就具有了以下功能:

  • 由BeanFactory接口提供的获取bean功能
  • 继承自单例bean注册接口默认实现类的添加、获取单例bean功能

既然是抽象类,自然也会有抽象方法,这个抽象bean工厂也提供了2个抽象方法供子类实现:获取bean定义方法 getBeanDefinition、创建bean方法 createBean

框架介绍完了,我们来思考为什么要这么去设计。这里实际上用到了设计模式中的【模板模式】。模板模式是指,一个抽象类中,有一个【主要的方法】,再定义1~n个方法,可以是抽象的,也可以是实际的。主要方法中会调用这些抽象方法,而这些抽象方法则由具体的子类去实现。那么这些抽象方法,其实就相当于【模板】。主要方法是我们最终想要达成的目的,而达成这个目的,需要一些步骤。在抽象类中,我们只在主要方法里去调用这些步骤,而不管这些步骤具体的实现,具体的实现由具体的子类去完成。就好像要让儿子去打酱油,在主方法中,只需要调用【打酱油】这个抽象方法,而具体打酱油要怎么做,走哪条路,去哪家店,则是由儿子这个子类来实现了。

那我们来具体分析这里的主要方法。很显然,这里的主要方法,自然是实现BeanFactory的获取bean方法。这里首先尝试根据名称,直接获取单例的bean,如果获取到了,那就直接返回。那么如果获取不到呢?我们就要去自己创建一个。怎么创建呢?先要获取bean定义,再根据bean定义来创建。所以下面的步骤就是:根据bean名称,获取bean定义,再通过bean定义来创建一个bean,最终返回创建好的bean。而获取bean定义、创建bean这两个方法都是抽象方法,在这里的感觉就是,步骤已经给你列好了,获取bean都需要经过这几步,具体实现我不管,反正最终能给我一个生产好的bean就行。

让抽象变具体:实现createBean方法!

上面我们的抽象类中定义了createBean这个抽象方法,它总要有人来具体实现。下面这个类,就是用来做这个事情的。

package com.akitsuki.springframework.beans.factory.support;import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;/*** 抽象的实例化Bean类,提供创建Bean的能力,创建完成后,放入单例bean map中进行缓存** @author ziling.wang@hand-china.com* @date 2022/11/7 10:12*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {@Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeanException {Object bean;try {//通过反射来创建Beanbean = beanDefinition.getBeanClass().newInstance();} catch (InstantiationException | IllegalAccessException e) {throw new BeanException("创建bean异常", e);}//放入单例Bean map中,以备调用addSingleton(beanName, bean);return bean;}
}

我们可以看到,它自己本身也是一个抽象类,继承了上面的抽象bean工厂,并且实现了createBean方法。这里槽点有些多,我们一个一个来解释。

  1. 这个类的名字好奇怪>_<!
    确实很奇怪,但这是为了和Spring保持一致而命名的。在Spring中,的确是由这个类来继承AbstractBeanFactory的,但这个类在现阶段过于简化了,没有体现出本身的功能。在这里可以简单的理解为为了实现createBean方法的一个子类(虽然名字有点怪)
  2. 为什么它也是一个抽象类,搞不懂诶>_<!
    我们可以看到,它只实现了父类的一个抽象方法。如果它是一个普通的类,那就要把两个方法全部实现了。
  3. 为什么不把父类的两个抽象方法都实现了啊,是太懒了吗>_<!
    这里体现出的是一种分层实现的方式,也是单一职责的一种体现。父类提供的2个抽象方法涉及到2个方面:beanbean定义。这个类只应该负责bean这一块,而bean定义,则交给更适合的人去负责,所以它只实现了创建bean的方法。

这里创建bean的实现,是简单的使用反射来实现的,从bean定义中拿到了class,调用了newInstance方法。这里其实无法处理有参构造方法。但这里为了简化,我们先不考虑这种情况,后面再继续完善。

关于bean定义,要做的事:注册接口

上面我们定义了一个接口单例bean注册接口,这个接口规定了注册一个单例bean,需要拥有哪些功能。那么,对于bean定义,是不是也需要这么一个注册接口呢?

package com.akitsuki.springframework.beans.factory.support;import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;/*** 注册Bean定义的接口** @author ziling.wang@hand-china.com* @date 2022/11/7 10:21*/
public interface BeanDefinitionRegistry {/*** 获取bean定义** @param beanName bean的名称* @return bean定义* @throws BeanException exception*/BeanDefinition getBeanDefinition(String beanName) throws BeanException;/*** 注册Bean定义** @param beanName       bean的名称* @param beanDefinition bean的定义*/void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
}

由于和单例bean注册接口类似,这里就不过多解释了。bean定义总是需要先注册,才能使用。这个我们在上一次完成小demo的时候,就已经明确了。所以这里我们用接口来规定bean定义注册时需要的功能:获取、注册。

最终的实现类!

上面我们定义了各种接口,也有各种抽象类或者普通类通过分层实现和继承,各自实现了许多功能。在最后,这些功能都需要落实在一个具体的实现类上。可以说,这个类是一个集大成的类,它通过逐级继承,拥有了前面所有描述的功能!

package com.akitsuki.springframework.beans.factory.support;import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;import java.util.HashMap;
import java.util.Map;/*** 最终集大成的核心类,通过逐级继承获取到了其他的各种bean操作方法* 同时也实现了BeanDefinitionRegistry的注册bean定义、获取bean定义的方法** @author ziling.wang@hand-china.com* @date 2022/11/7 10:20*/
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();@Overridepublic BeanDefinition getBeanDefinition(String beanName) throws BeanException {BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if (null == beanDefinition) {throw new BeanException("没有名称为" + beanName + "的Bean定义");}return beanDefinition;}@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {beanDefinitionMap.put(beanName, beanDefinition);}
}

终于到了大boss了。但是这个类看起来意外的很简单。因为它所拥有的功能,都被各个父类进行分担了。而对于它自己来说,则是实现bean定义注册接口里面的两个方法即可。可能这就是继承的力量吧(笑)。

而这里的beanDefinition操作,则与上次练习中的beanFactory类似,也比较好理解,就不多解释了。

最后我们来分析一下这个类最终所拥有的功能,再次感受一下继承的力量吧~

  1. beanDefinition的操作能力:自身实现 BeanDefinitionRegistry接口所拥有
  2. 创建bean:继承自 AbstractAutowireCapableBeanFactory
  3. 获取bean:继承自 AbstractBeanFactory
  4. 注册、获取单例bean:继承自 DefaultSingletonBeanRegistry

嗯嗯,真是了不起。有了这么个集大成的类,我们这次的demo也就算完成了。下面准备进行测试。顺带一提,这个类的名字也很奇怪,但这也是为了和Spring保持一致,原因参照上面的解释吧~

终于,要开始测试了!

经过了上面这些兜兜转转、逐级继承,最终我们有了DefaultListableBeanFactory这么个集万千宠爱于一身的类。还记得这一章我们的目标是什么吗?对,实现Bean的定义、注册、获取,并且让Spring容器来帮我们进行bean的实例化!

首先,来把我们的bean稍微改造一下吧~

package com.akitsuki.springframework.test.bean;/*** @author ziling.wang@hand-china.com* @date 2022/11/7 10:30*/
public class UserService {public UserService() {System.out.println("userService初始化");}public void queryUserInfo() {System.out.println("查询用户信息");}
}

其实也没有什么改造,只不过加了个无参的构造方法而已,这样做,是为了更好的看出Spring帮助我们创建bean的过程。

接下来是正式的测试方法!

package com.akitsuki.springframework.test;import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;
import com.akitsuki.springframework.beans.factory.support.DefaultListableBeanFactory;
import com.akitsuki.springframework.test.bean.UserService;
import org.junit.Test;/*** @author ziling.wang@hand-china.com* @date 2022/11/7 10:31*/
public class ApiTest {@Testpublic void testBeanFactory() throws BeanException {//初始化BeanFactoryDefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();//注册BeanBeanDefinition beanDefinition = new BeanDefinition(UserService.class);beanFactory.registerBeanDefinition("userService", beanDefinition);//获取BeanUserService userService = (UserService) beanFactory.getBean("userService");userService.queryUserInfo();//第二次获取BeanuserService = (UserService) beanFactory.getBean("userService");userService.queryUserInfo();}
}

测试结果:

userService初始化
查询用户信息
查询用户信息Process finished with exit code 0

可以看到,在这次的测试中,我们首先初始化了我们那个集大成的bean工厂:DefaultListableBeanFactory。然后,将bean的class作为参数,创建了bean的定义,并注册到了Spring中。接下来,我们只需要简单地对工厂说:我需要一个名为"userService"的bean。就可以等着工厂帮我们生产了。这里从工厂获取了2次,但结果中只初始化了1次。可以看出,在第一次获取bean的时候,工厂会进行新建操作。而第二次进行获取的时候,工厂直接从缓存中将第一次创建的bean进行了返回,这就是单例bean的思想。到这里,我们的第二章手写Spring也要告一段落了。整个工程看起来也要比第一次更加像样了。

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

手写Spring-第二章-实现 Bean 的定义、注册、获取相关推荐

  1. 从零开始手写VIO 第二章 IMU传感器

    第二章 IMU传感器 课程代码: https://github.com/kahowang/Visual_Internal_Odometry/tree/main/%E7%AC%AC%E4%BA%8C%E ...

  2. 第二章 装配Bean(Spring in action,3th)

                                         第二章 装配Bean 创建应用对象之间协作关系的行为通常被称为装配(wiring),是依赖注入的本质. XML方式声明Bean ...

  3. 手写Spring DI依赖注入,嘿,你的益达!

    手写DI 提前实例化单例Bean DI分析 DI的实现 构造参数依赖 一:定义分析 二:定义一个类BeanReference 三:BeanDefinition接口及其实现类 四:DefaultBean ...

  4. 从头开始实现一个小型spring框架——手写Spring之集成Tomcat服务器

    手写Spring之集成Tomcat与Servlet 写在前面 一.Web服务模型及servlet 1.1 Web服务器 1.2 请求流程 二.实现 三.小结 写在前面 最近学习了一下spring的相关 ...

  5. 手写 Spring 事务、IOC、DI 和 MVC

    Spring AOP 原理 什么是 AOP? AOP 即面向切面编程,利用 AOP 可以对业务进行解耦,提高重用性,提高开发效率 应用场景:日志记录,性能统计,安全控制,事务处理,异常处理 AOP 底 ...

  6. 手把手教你手写Spring框架

    手写spring准备工作: 新建一个maven工程: 架构 新建类: package com.spring;public class keweiqinApplicationContext {priva ...

  7. 手写 Spring MVC

    学习自<Spring 5核心原理与30个类手写实战>作者 Tom 老师 手写 Spring MVC 不多说,简历装 X 必备.不过练好还是需要求一定的思维能力. 一.整体思路 思路要熟练背 ...

  8. 05. 手写Spring核心框架

    目录 05 手写Spring核心框架 Pt1 手写IoC/DI Pt1.1 流程设计 Pt1.2 基础配置 application.properties pom.xml web.xml Pt1.3 注 ...

  9. JAVA项目代码手写吗_一个老程序员是如何手写Spring MVC的

    见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十多 ...

最新文章

  1. attachEvent 与addEventListener到底有什么区别呢?
  2. GNOME 的新夜灯功能旨在帮助你睡得更好
  3. hibernate jpa_JPA /Hibernate刷新策略初学者指南
  4. python定时下载链接_python定时下载FTP指定文件
  5. Vue第三部分(1):Vue脚手架构建过程详细介绍和案例
  6. kettle 数据提取效率提升
  7. NYOJ题目170网络的可靠性
  8. [Iphone开发]如何在GDB中查看变量的值
  9. java知识总结-19
  10. c# .net object对象与json字符串互转换
  11. html yy直播,网页YY直播间进入方法 网页YY迷你版怎么用
  12. 智能机器人助力智慧城市建设
  13. zmud命令详细解答
  14. 生命旅程中何生命个体
  15. cocos2d-x lua 框架中 self.super.ctor(self, app) 和 self.super:ctor(app) 的区别
  16. elasticsearch索引、文档、映射等概念
  17. 5GNR漫谈8:CSI-RS/TRS/SRS参考信号
  18. CREO枕头模型制作
  19. 王者荣耀吃鸡气泡等等头像框DIY在线生成N种风格微信小程序源码下载
  20. 官宣!香港大学,将落户深圳

热门文章

  1. 背靠上市公司的斑点猫,做超级猫眼的目的其实不是卖猫眼?
  2. ICPC Camp 2016 几道神奇的构造打表题
  3. Ardupilot 编译问题汇总
  4. 南航推出特色餐食 温暖旅客春节归家路
  5. MS PPT一键设置全部文本框字体和大小(VBA)
  6. 亿级流量电商详情页系统设计与实战-虚拟机centos环境搭建
  7. 浙江在日留学生尚无伤亡报告
  8. 从零开始缓慢深入Linux - 基础指令篇(3)
  9. Mini WinMount V0.2
  10. Mplus教程:如何做潜在类别分析LCA