五分钟,手撸一个简单的Spring容器
工厂和Spring容器
Spring是一个成熟的框架,为了满足扩展性、实现各种功能,所以它的实现如同枝节交错的大树一样,现在让我们把视线从Spring本身移开,来看看一个萌芽版的Spring容器怎么实现。
Spring的IOC本质就是一个大工厂,我们想想一个工厂是怎么运行的呢?
————————————————
生产产品:一个工厂最核心的功能就是生产产品。在Spring里,不用Bean自己来实例化,而是交给Spring,应该怎么实现呢?——答案毫无疑问,反射。
那么这个厂子的生产管理是怎么做的?你应该也知道——工厂模式。
库存产品:工厂一般都是有库房的,用来库存产品,毕竟生产的产品不能立马就拉走。Spring我们都知道是一个容器,这个容器里存的就是对象,不能每次来取对象,都得现场来反射创建对象,得把创建出的对象存起来。
订单处理:还有最重要的一点,工厂根据什么来提供产品呢?订单。这些订单可能五花八门,有线上签签的、有到工厂签的、还有工厂销售上门签的……最后经过处理,指导工厂的出货。
在Spring里,也有这样的订单,它就是我们bean的定义和依赖关系,可以是xml形式,也可以是我们最熟悉的注解形式。
————————————————
涉及到需要的事物有:

订单:Bean定义
Bean可以通过一个配置文件定义,我们会把它解析成一个类型。
userDao = com.example.apidemo.spring.UserDao

BeanConfig.java
bean定义类,配置文件中bean定义对应的实体
package com.example.apidemo.spring; import lombok.Data; /**
* bean定义类,配置文件中bean定义对应的实体
*/
@Data
public class BeanConfig { private String beanName; private Class beanClass;
//省略getter、setter
}
获取订单:资源加载
接下订单之后,就要由销售向生产部门交接,让生产部门知道商品的规格、数量之类。
资源加载器,就是来完成这个工作的,由它来完成配置文件中配置的加载。
/**
* 获取订单:资源加载
* @return
*/
public class ResourceLoader { public static Map<String, BeanConfig> getResource() {
Map<String, BeanConfig> beanDefinitionMap = new HashMap<>(16);
Properties properties = new Properties();
try {
//配置beans.properties文件: userDao = com.example.apidemo.spring.UserDao
InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties");
properties.load(inputStream);
Iterator<String> it = properties.stringPropertyNames().iterator();
while (it.hasNext()) {
String key = it.next();
String className = properties.getProperty(key);
BeanConfig beanConfig = new BeanConfig();
beanConfig.setBeanName(key);
Class clazz = Class.forName(className);
beanConfig.setBeanClass(clazz);
beanDefinitionMap.put(key, beanConfig);
}
inputStream.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return beanDefinitionMap;
} }
订单分配:Bean注册
对象注册器,这里用于单例bean的缓存,我们大幅简化,默认所有bean都是单例的。可以看到所谓单例注册,也很简单,不过是往HashMap里存对象。
/**
* 仓库:订单分配:Bean注册
*/
public class BeanRegister { //单例Bean缓存
private Map<String, Object> singletonMap = new HashMap<>(32); /**
* 获取单例Bean
*
* @param beanName bean名称
* @return
*/
public Object getSingletonBean(String beanName) {
return singletonMap.get(beanName);
} /**
* 注册单例bean
*
* @param beanName
* @param bean
*/
public void registerSingletonBean(String beanName, Object bean) {
if (singletonMap.containsKey(beanName)) {
return;
}
singletonMap.put(beanName, bean);
} }
生产车间:对象工厂
好了,到了我们最关键的生产部门了,在工厂里,生产产品的是车间,在IOC容器里,生产对象的是BeanFactory。
对象工厂,我们最核心的一个类,在它初始化的时候,创建了bean注册器,完成了资源的加载。
获取bean的时候,先从单例缓存中取,如果没有取到,就创建并注册一个bean
/**
* 生产车间:对象工厂
* 流程:《生产车间:对象工厂》==执行BeanFactory()初始化方法,ResourceLoader().getResource()去==《获取订单:资源加载》
* 返回一个BeanFactory,里面包含配置的bean实例。 如果第一次调用,就是==《仓库:订单分配:Bean注册》,用BeanRegister去注册一个单例的bean,
* 并且存储到BeanRegister,如果第二次调用,先从缓存beanRegister取,获取不到再注册。
*/
public class BeanFactory { private Map<String, BeanConfig> 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定义,创建bean
return createBean(beanDefinitionMap.get(beanName));
} /**
* 创建Bean
*
* @param beanConfig bean定义
* @return
*/
private Object createBean(BeanConfig beanConfig) {
try {
Object bean = beanConfig.getBeanClass().newInstance();
//缓存bean
beanRegister.registerSingletonBean(beanConfig.getBeanName(), bean);
return bean;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
生产销售:测试
UserDao.java
我们的Bean类,很简单
/**
* 生产销售:测试
*/
public class UserDao {
public void queryUserInfo(){
System.out.println("new A good man.");
}
}
单元测试:
import org.junit.Test;
public class ApiTest {
/**
* 演示一个简单的spring容器框架
*/
@Test
public 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();
}
}
结果:

五分钟,手撸一个简单的Spring容器的更多相关文章
- 深入理解Spring--动手实现一个简单的SpringIOC容器
接触Spring快半年了,前段时间刚用Spring4+S2H4做完了自己的毕设,但是很明显感觉对Spring尤其是IOC容器的实现原理理解的不到位,说白了,就是仅仅停留在会用的阶段,有一颗想读源码的心 ...
- 【RPC】手撸一个简单的RPC框架实现
涉及技术 序列化.Socket通信.Java动态代理技术,反射机制 角色 1.服务提供者:运行在服务端,是真实的服务实现类 2.服务发布监听者:运行在RPC服务端,1将服务端提供的 ...
- 五分钟,手撸一个Spring容器!
大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌. 这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开S ...
- 《Spring 手撸专栏》第 2 章:小试牛刀(让新手能懂),实现一个简单的Bean容器
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 上学时,老师总说:不会你就问,但多数时候都不知道要问什么! 你总会在小傅哥的文章前言 ...
- 手撸一个springsecurity,了解一下security原理
手撸一个springsecurity,了解一下security原理 转载自:www.javaman.cn 手撸一个springsecurity,了解一下security原理 今天手撸一个简易版本的sp ...
- 通过 Netty、ZooKeeper 手撸一个 RPC 服务
说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...
- 手撸一个SpringBoot-Starter
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- 【手撸一个ORM】MyOrm的使用说明
[手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...
- 利用SpringBoot+Logback手写一个简单的链路追踪
目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...
随机推荐
- #ifndef、#define、#ifdef等命令在C#中的应用
1.#ifndef指令的理解 #ifndef是一个预处理指令,用于判断一个表示符是否被定义.如果该标识符未被定义,则执行条件编译中的代码块.否则,将跳过该代码块.下面是一个简单的粒子. 1 #ifnd ...
- 文心一言 VS 讯飞星火 VS chatgpt (149)-- 算法导论12.2 4题
五.用go语言,证明:如果一棵二叉搜索树中的一个结点有两个孩子,那么它的后继没有左孩子,它的前驱没有右孩子. 灵捷3.5 . 文心一言: 为了证明这个性质,我们首先需要明确二叉搜索树(BST)的定义和 ...
- Rong晔大佬教程学习(0):前言
2023-12-13 在安装了tinyriscv的工具链之后,本想着说去看那个技术文档,但是那个技术文档只是相当于一个"使用手册",而不是技术教程,所以说还是得去补一补计组的知识. ...
- JavaScript 文件优化指南
本文将探讨实用的 JavaScript 文件优化技术.如何处理与 JavaScript 文件相关的性能问题以及帮助优化过程的工具.你将获得提升web应用程序速度的相关知识,从而为你的用户提供无缝体验. ...
- 华为防火墙1day?
背景信息 缺省情况下,FW通过8887端口提供内置的本地Portal认证页面,用户可以主动访问或HTTP重定向至认证页面(https://接口IP地址:8887)进行本地Portal认证. 当企业部署 ...
- Java8函数式编程应用
我们经常提到,Java8是革命性的一个版本,原因就是正式引入了函数式编程,那Java的函数式编程在实际应用中到底有什么用呢?结合实际的应用,我整理出了函数式在Java的几个经典用途. 缓求值 惰性求值 ...
- 为什么说数字孪生和GIS高度互补?它们各自从对方那里获得了什么?
在数字化时代,数字孪生和GIS作为两项重要技术,它们的融合正日益受到人们的关注和认可.数字孪生是将实体世界与数字世界紧密结合的技术,可以创建实时的虚拟副本,对物理系统进行模拟.优化和预测.而GIS则是 ...
- Cesium被接入数字孪生系统后会发生怎样的改变?
众所周知,Cesium凭借其开源免费的特点一直垄断着整个三维GIS的生态系统,但是随着数字孪生技术的发展以及各项新需求的不断涌现,Cesium与数字孪生系统相结合的潜力也逐渐凸显. 一般而言,Cesi ...
- Collection和Map的区别
Collection接口,包含list和set子接口Collection和Map接口之间的主要区别在于:Collection中存储了一组对象,而Map存储关键字/值对.在Map对象中,每一个关键字最多 ...
- Git和Github库详细使用教程
SVN 是集中式或者有中心式版本控制系统,版本库是集中放在中央服务器的; Git 是分布式版本控制系统,那么它就没有中央服务器的,每个人的电脑就是一个完整的版本库,这样,工作的时候就不需要联网了,因为 ...