五分钟,手撸一个Spring容器!
大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌。
这节,我们回归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的定义和依赖关系,可以是xml形式,也可以是我们最熟悉的注解形式。
那对应我们的萌芽版的Spring容器是什么样的呢?
订单:Bean定义
Bean可以通过一个配置文件定义,我们会把它解析成一个类型。
beans.properties
为了偷懒,这里直接用了最方便解析的properties,用一个<key,value>类型的配置来代表Bean的定义,其中key是beanName,value是class
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<String, BeanDefinition> getResource() {
Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(16);
Properties properties = new Properties();
try {
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);
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<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
public class BeanFactory { private Map<String, BeanDefinition> 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 beanDefinition bean定义
* @return
*/
private Object createBean(BeanDefinition beanDefinition) {
try {
Object bean = beanDefinition.getBeanClass().newInstance();
//缓存bean
beanRegister.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 {
@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();
}
}
运行结果
A good man.
A good man.
至此,我们一个萌芽版的Spring容器就完成了。
考虑一下,它有哪些不足呢?是否还可以抽象、扩展、解耦……
细细想想这些东西,你是不是对真正的Spring IOC容器为何如此复杂,有所理解了呢?
参考:
[1]. 《Spring揭秘》
[2].小傅哥 《手撸Spring》
[3].《精通Spring4.X企业应用开发实战》
五分钟,手撸一个Spring容器!的更多相关文章
- 手撸一个IOC容器
IoC 什么是IoC? IoC是Inversion of Control(控制反转)的简称,注意它是一个技术思想.描述的是对象创建.管理的事情. 传统开发方式:比如类A依赖类B,往往会在类A里面new ...
- 曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器
一.前言 一共8个类,撸一个IOC容器.当然,我们是很轻量级的,但能够满足基本需求.想想典型的 Spring 项目,是不是就是各种Service/DAO/Controller,大家互相注入,就组装成了 ...
- 通过 Netty、ZooKeeper 手撸一个 RPC 服务
说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...
- 手撸一个SpringBoot-Starter
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
- 手撸一个springsecurity,了解一下security原理
手撸一个springsecurity,了解一下security原理 转载自:www.javaman.cn 手撸一个springsecurity,了解一下security原理 今天手撸一个简易版本的sp ...
- 【手撸一个ORM】MyOrm的使用说明
[手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- 第二篇-用Flutter手撸一个抖音国内版,看看有多炫
前言 继上一篇使用Flutter开发的抖音国际版 后再次撸一个国内版抖音,大部分功能已完成,主要是Flutter开发APP速度很爽, 先看下图 项目主要结构介绍 这次主要的改动在api.dart 及 ...
- C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架
C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架 如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongo ...
随机推荐
- 使用.NET 6开发TodoList应用(填坑1)——实现当前登录用户获取
系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 在前面的文章里使用.NET 6开发TodoList应用(5)--领域实体创建,我们留了一个坑还没有填上,就是在数据库保存的时候 ...
- 【reverse】逆向3 寻找地址
[reverse]逆向3 寻找地址 寻址公式一:[立即数] 读取内存的值: mov eax,dword prt ds:[0x13FFC4] 将内存编号为0x13FFC4.0x13FFC5.0x13FF ...
- 【笔记】macos上部署thanos_receiver + thanos_query
为了方便起见,在mac笔记本上进行了测试 1.写一个发送数据的客户端 package main import ( "fmt" "io/ioutil" " ...
- centos6下php53升级为php7
1.查看版本 [root@web-1 blog]# php -v No log handling enabled - turning on stderr logging Created directo ...
- 论文解读(DFCN)《Deep Fusion Clustering Network》
Paper information Titile:Deep Fusion Clustering Network Authors:Wenxuan Tu, Sihang Zhou, Xinwang Liu ...
- 运用Spring Aop,一个注解实现日志记录
运用Spring Aop,一个注解实现日志记录 1. 介绍 我们都知道Spring框架的两大特性分别是 IOC (控制反转)和 AOP (面向切面),这个是每一个Spring学习视频里面一开始都会提到 ...
- c#代码设计:子类和父类
哭辽,事情是这样的 我想写个产品类用来放点相机参数,想类似这种的使用方式:(时间关系不改了,产品=Zoo,animals=相机) Zoo Zooxx= new Zoo (); Zoo.Animals ...
- ApacheCN 深度学习译文集 20210125 更新
新增了七个教程: PyTorch 中文官方教程 1.7 学习 PyTorch PyTorch 深度学习:60 分钟的突击 张量 torch.autograd的简要介绍 神经网络 训练分类器 通过示例学 ...
- 统信UOS系统部署.Net Core 5.0
平时很少用Linux,需要的时候才查资料,记录下遇到的问题和解决办法.这次部署的系统是统信UOS,arm64位CPU 第一步:安装.Net Core 5.0运行环境 统信UOS是基于Debian 10 ...
- DESUtil
package com.tebon.ams.util;import sun.misc.BASE64Decoder;import javax.crypto.Cipher;import javax.cry ...