大家好,我是老三,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容器!的更多相关文章

  1. 手撸一个IOC容器

    IoC 什么是IoC? IoC是Inversion of Control(控制反转)的简称,注意它是一个技术思想.描述的是对象创建.管理的事情. 传统开发方式:比如类A依赖类B,往往会在类A里面new ...

  2. 曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器

    一.前言 一共8个类,撸一个IOC容器.当然,我们是很轻量级的,但能够满足基本需求.想想典型的 Spring 项目,是不是就是各种Service/DAO/Controller,大家互相注入,就组装成了 ...

  3. 通过 Netty、ZooKeeper 手撸一个 RPC 服务

    说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...

  4. 手撸一个SpringBoot-Starter

    1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...

  5. 手撸一个springsecurity,了解一下security原理

    手撸一个springsecurity,了解一下security原理 转载自:www.javaman.cn 手撸一个springsecurity,了解一下security原理 今天手撸一个简易版本的sp ...

  6. 【手撸一个ORM】MyOrm的使用说明

    [手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...

  7. 使用Java Socket手撸一个http服务器

    原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...

  8. 第二篇-用Flutter手撸一个抖音国内版,看看有多炫

    前言 继上一篇使用Flutter开发的抖音国际版 后再次撸一个国内版抖音,大部分功能已完成,主要是Flutter开发APP速度很爽,  先看下图 项目主要结构介绍 这次主要的改动在api.dart 及 ...

  9. C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架

    C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架 如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongo ...

随机推荐

  1. docker 修改容器env配置

    docker 修改容器env配置 场景:修改zabbix数据库密码 zabbix容器构成: 数据库:zabbix-mysql server端:zabbix-server-mysql web端:zabb ...

  2. golang中内存地址计算-根据内存地址获取下一个内存地址对应的值

    package main import ( "fmt" "unsafe" ) func main() { // 根据内存地址获取下一个字节内存地址对应的值 da ...

  3. gin中jsonp的用法

    package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := ...

  4. 『无为则无心』Python基础 — 41、Python中文件的读写操作(一)

    目录 1.文件操作步骤 2.文件的读写操作 (1)文件的打开 (2)打开文件模式 (3)获取一个文件对象 (4)关于文件路径 1.文件操作步骤 当我们要读取或者写入文件时,我们需要打开文件,在操作完毕 ...

  5. 『无为则无心』Python基础 — 42、Python中文件的读写操作(二)

    目录 (5)文件对象方法(重点) 1)写方法 2)读方法 3)seek()方法 4)tell()方法 (6)关闭 (7)综合练习:读取大文件 (5)文件对象方法(重点) 1)写方法 @1.语法 对象对 ...

  6. 学习MyBatis必知必会(6)~Mapper基础的拓展

    一.typeAlias 类型别名[自定义别名.系统自带别名] 1.类型别名:为 Java 类型设置一个缩写名字. 它仅用于 XML 配置,意在降低冗余的全限定类名书写 2.配置自定义别名: (1)方式 ...

  7. Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  8. HttpClient 的Timeout waiting for connection from pool

    Timeout waiting for connection from pool 异常 httpClient大家用到地方会很多,先简单描述一下几个关键配置的意义 httpClient版本为4.5.1 ...

  9. linux 设置connect 超时

    转载请注明来源:https://www.cnblogs.com/hookjc/ 将一个socket 设置成阻塞模式和非阻塞模式,使用fcntl方法,即: 设置成非阻塞模式: 先用fcntl的F_GET ...

  10. NSMutableArray基本概念

    1.NSMutableArray介绍 什么是NSMutableArray NSMutableArray是NSArray的子类 NSArray是不可变的,一旦初始化完毕后,它里面的内容就永远是固定的, ...