五分钟手撸一个Spring容器(萌芽版)
目录
- 从什么是IOC开始?
- 工厂和Spring容器
- 订单:Bean定义
- 获取订单:资源加载
- 订单分配:Bean注册
- 生产车间:对象工厂
- 生产销售:测试
这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开Spring神秘的面纱!
从什么是IOC开始? Spring——春天,Java编程世界的春天是由一位音乐家——Rod Johnson带来的。
Rod Johnson先后编写了两本巨著《Expert One-on-One J2EE Design and Development》、《Expert One-on-One J2EE Development without EJB》,拉起了挑战正统Java EE框架EJB的大旗。
文章图片
Rod Johnson不仅是一名旗手,更是开发了Spring这一轻量级框架,像一名勇敢的龙骑兵一样,对EJB发动了冲锋,并最终战胜了EJB,让Spring成为Java EE事实上的标准。
文章图片
Spring的两大内核分别是IOC和AOP,其中最最核心的是IOC。
所谓的IOC(控制反转):就是由容器来负责控制对象的生命周期和对象间的关系。以前是我们想要什么,就自己创建什么,现在是我们需要什么,容器就给我们送来什么。
文章图片
也就是说,控制对象生命周期的不再是引用它的对象,而是容器。对具体对象,以前是它控制其它对象,现在所有对象都被容器控制,所以这就叫控制反转。
文章图片
也许你还听到另外一个概念DI(依赖注入),它指的是容器在实例化对象的时候把它依赖的类注入给它,我们也可以认为,DI是IOC的补充和实现。
工厂和Spring容器 Spring是一个成熟的框架,为了满足扩展性、实现各种功能,所以它的实现如同枝节交错的大树一样,现在让我们把视线从Spring本身移开,来看看一个萌芽版的Spring容器怎么实现。
Spring的IOC本质就是一个大工厂,我们想想一个工厂是怎么运行的呢?
文章图片
- 生产产品:一个工厂最核心的功能就是生产产品。在Spring里,不用Bean自己来实例化,而是交给Spring,应该怎么实现呢?——答案毫无疑问,反射。
- 库存产品:工厂一般都是有库房的,用来库存产品,毕竟生产的产品不能立马就拉走。Spring我们都知道是一个容器,这个容器里存的就是对象,不能每次来取对象,都得现场来反射创建对象,得把创建出的对象存起来。
- 订单处理:还有最重要的一点,工厂根据什么来提供产品呢?订单。这些订单可能五花八门,有线上签签的、有到工厂签的、还有工厂销售上门签的……最后经过处理,指导工厂的出货。
那对应我们的萌芽版的Spring容器是什么样的呢?
文章图片
订单:Bean定义
Bean可以通过一个配置文件定义,我们会把它解析成一个类型。
文章图片
beans.properties
为了偷懒,这里直接用了最方便解析的properties,用一个
userDao:cn.fighter3.bean.UserDao
BeanDefinition.java
bean定义类,配置文件中bean定义对应的实体
public class BeanDefinition {private String beanName; private Class beanClass; //省略getter、setter }
获取订单:资源加载
接下订单之后,就要由销售向生产部门交接,让生产部门知道商品的规格、数量之类。
资源加载器,就是来完成这个工作的,由它来完成配置文件中配置的加载。
public class ResourceLoader {public static Map getResource() {Map beanDefinitionMap = new HashMap<>(16); Properties properties = new Properties(); try {InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties"); properties.load(inputStream); Iterator it = properties.stringPropertyNames().iterator(); while (it.hasNext()) {String key = it.next(); String className = properties.getProperty(key); BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setBeanName(key); Class clazz = Class.forName(className); beanDefinition.setBeanClass(clazz); beanDefinitionMap.put(key, beanDefinition); }inputStream.close(); } catch (IOException | ClassNotFoundException e) {e.printStackTrace(); }return beanDefinitionMap; }}
订单分配:Bean注册
对象注册器,这里用于单例bean的缓存,我们大幅简化,默认所有bean都是单例的。可以看到所谓单例注册,也很简单,不过是往HashMap里存对象。
public class BeanRegister {//单例Bean缓存private Map singletonMap = new HashMap<>(32); /*** 获取单例Bean** @param beanName bean名称* @return*/public Object getSingletonBean(String beanName) {return singletonMap.get(beanName); }* 注册单例bean* @param beanName* @param beanpublic void registerSingletonBean(String beanName, Object bean) {if (singletonMap.containsKey(beanName)) {return; }singletonMap.put(beanName, bean); }
生产车间:对象工厂
好了,到了我们最关键的生产部门了,在工厂里,生产产品的是车间,在IOC容器里,生产对象的是BeanFactory。
文章图片
- 对象工厂,我们最核心的一个类,在它初始化的时候,创建了bean注册器,完成了资源的加载。
- 获取bean的时候,先从单例缓存中取,如果没有取到,就创建并注册一个bean
public class BeanFactory {private Map beanDefinitionMap = new HashMap<>(); private BeanRegister beanRegister; public BeanFactory() {//创建bean注册器beanRegister = new BeanRegister(); //加载资源this.beanDefinitionMap = new ResourceLoader().getResource(); }/*** 获取bean** @param beanName bean名称* @return*/public Object getBean(String beanName) {//从bean缓存中取Object bean = beanRegister.getSingletonBean(beanName); if (bean != null) {return bean; }//根据bean定义,创建beanreturn createBean(beanDefinitionMap.get(beanName)); }/*** 创建Bean** @param beanDefinition bean定义* @return*/private Object createBean(BeanDefinition beanDefinition) {try {Object bean = beanDefinition.getBeanClass().newInstance(); //缓存beanbeanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean); return bean; } catch (InstantiationException | IllegalAccessException e) {e.printStackTrace(); }return null; }}
生产销售:测试
UserDao.java
我们的Bean类,很简单
public class UserDao {public void queryUserInfo(){System.out.println("A good man."); }}
单元测试
public class ApiTest {@Testpublic void test_BeanFactory() {//1.创建bean工厂(同时完成了加载资源、创建注册单例bean注册器的操作)BeanFactory beanFactory = new BeanFactory(); //2.第一次获取bean(通过反射创建bean,缓存bean)UserDao userDao1 = (UserDao) beanFactory.getBean("userDao"); userDao1.queryUserInfo(); //3.第二次获取bean(从缓存中获取bean)UserDao userDao2 = (UserDao) beanFactory.getBean("userDao"); userDao2.queryUserInfo(); }}
运行结果
A good man.至此,我们一个萌芽版的Spring容器就完成了。
A good man.
考虑一下,它有哪些不足呢?是否还可以抽象、扩展、解耦……
细细想想这些东西,你是不是对真正的Spring IOC容器为何如此复杂,有所理解了呢?
参考:
[1]. 《Spring揭秘》
[2].小傅哥 《手撸Spring》
[3].《精通Spring4.X企业应用开发实战》
【五分钟手撸一个Spring容器(萌芽版)】到此这篇关于五分钟,手撸一个Spring容器的文章就介绍到这了,更多相关Spring容器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- 从零开始,开发一个|从零开始,开发一个 Web Office 套件(7)(新的问题—— Click 事件的 z-index)
- 深度学习|关于yolov5加注意力之后的yaml文件讲解(5.0版本和6.0版本通用),文末有一个大部分人都没注意到的细节
- 发现Spring事务的一个实锤bug,官方还拒不承认(你来评评理...)
- 监控平台技术调研
- c语言|立下一个flag(学好编程)
- 详解WCF服务中的svc文件
- Leetcode 704 二分查找
- low-code
- python写计算机模拟器_用 Python 写出了一个 Gameboy 模拟器
- 聊聊第一个开源项目(内网穿透) - CProxy