手写Spring核心 -- 如何实现一个最简单的spring容器

Posted by klaus_turbo on 2022-09-03
Spring

手写Spring核心 – 如何实现一个最简单的spring容器

前言

工作了这么久,每当做的工作内容是和应用开发相关的,就抛不开 spring 这个话题。甚至在一些面试中,面试官还是不停的在问:“spring 是什么?怎么解决循环依赖?”巴拉巴拉的一大堆和spring相关的问题。当然背过八股文,看过面经的对这种问题回答起来都是溜到飞起,但如果换一个面经没有的,八股文没有的,你是不是还能回答的出来?或者说在实际的应用场景中,你对spring真正的理解,决定了你在开发或者解决问题的时候站在什么样子的角度去思考解决这个事情。于是在某一个深夜,我决定去一探 spring 的庐山真面目,然后手写一个 spring。为什么要这么做呢?其实作为一个实打实的理科生,深知对于所有的技术,靠死记硬背是不行的,必须以理科思维理解为目的的学会。剔除 Spring 源码中繁杂的部分,以 IOC 为主体去逐步的实现 Spring 中比较核心的内容。

废话不多说,我们直接进入正题。

Spring bean容器是什么?

要回答这个问题,我们首先要知道,Spring容器实际真正做的是什么事情。在spring容器中,它包含并管理着应用对象的配置和生命周期,在这个意义上,spring其实是一种用于承载对象的容器,我们可以配置每一个Bean对象是如何创建,这些bean可以创建成一个单独的实例或者每次需要的时候都生成一个新的实例,以及他们是如何相互关联构建和使用的。

如果一个bean对象交给spring容器管理,那么这个bean对象就应该以零件的方式被拆解后存放到bean的定义中,这就相当于是一种把bean对象解耦的操作,可以由spring更加容易的管理,这样的将实际bean对象以某种方式拆解后保存的方式也是前面我们提到的循环依赖能够得到解决的根本之一。

当一个bean对象被定义存放之后,再由spring统一进行装配,这个过程包括bean的初始化,属性填充等,在种种的操作结束之后,我们就可以通过我们熟悉的getBean等api去完整的使用一个bean实例化之后的对象了。

说了这么多,所以你能总结出这个问题的答案了吗?

实践

我们一直在说spring容器,spring容器,spring容器。那必然就是说这个spring是可以存放我们的bean对象,所以才会称之为容器的。所以,我认为所有可以存放数据的具体的数据结构的实现,都可以成为容器,例如:ArrayList,HashSet等,在我们spring bean容器的场景下,使用HashMap来作为我们最底层的容器实现最合适不过了。

从以往使用的经历来说,完成一个基础的spring bean容器的实现,需要bean的定义,注册,获取这么三个基本的步骤:

  • 定义:BeanDefinition,可能你在看spring源码的时候这是出现的最多的一个类,它会包括诸如singleton,prototype,BeanClassName等,但现在我们不会将它定义的太复杂。
  • 注册:这个过程就相当于我们把数据放入到hashmap的过程,只不过现在hashmap存放的是定义了bean的对象信息。
  • 获取:一切的一切都是为了使用这个bean,所以最后一步就是获取对象,其中key是beanName,当spring容器初始化完毕之后,就可以通过api去获取了。

首先我们先定义一个最简单的容器实现,用来展示上面的这三个步骤,类图如下:

spring01_关系类图.png

Spring Bean容器的整个实现内容非常简单,也仅仅是包括了一个简单的BeanFactory和BeanDefinition,这里的类名与spring源码保持一致,只不过现在的实现非常的简单。

Bean定义

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BeanDefinition{

private Object bean;

public BeanDefinition(Object bean){
this.bean = bean;
}

public Object getBean(){
return bean;
}

}

目前的bean定义中们只有一个Object用于存放Bean对象,在后面我们会逐步完善。

Bean工厂

看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BeanFactory{

private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

public Object getBean(String name){
return beanDefinitionMap.get(name).getBean();
}

public void registerBeanDefinition(String name,BeanDefinition beanDefinition){
beanDefinitionMao.put(name,beanDefinition);
}

}

在bean工厂的实现中,包括了注册和获取bean,这里注册的是bean的定义信息。

好了,这就是最简单的spring容器的实现。可能有人要说了,你这不是忽悠人么?虽然我们这里实现的非常简单,但这种极其简单的实现内容就是整个spring容器中关于bean使用的最终效果,也是其基本最核心的原理。当然,这样的实现是不适用于生产的,后续会慢慢的扩充。

我们来写点测试的代码,看看我们的容器是否能完成我们所需要的功能。

先定义一个我们需要的bean:

1
2
3
4
5
6
7
public class UserService{

public void queryUserInfo(){
System.out.println("查询用户信息")
}

}

然后是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test(){
// 1.初始化 BeanFactory
BeanFactory beanFactory = new BeanFactory();

// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition(new UserService());
beanFactory.registerBeanDefinition("userService", beanDefinition);

// 3.获取bean
UserService userService = (UserService) beanFactory.getBean("userService");
userService.queryUserInfo();
}

看一下运行结果:

spring01_调试结果.png

ok,已经实现了我们所需要的功能,通过初始化容器,构造BeanDefinition,注册Bean的BeanDefinition,我们最终便可以通过容器来获取我们的Bean并使用它。

当然,这边实现的BeanFactory是我们的一个雏形,相对于这部分内容不足以难倒任何人。

好了,我们关于实现最简单spring容器的部分就到此结束了,如果你有兴趣,那就一起来探讨接下来的内容吧。

本人关于图片作品版权的声明:

  1. 本人在此刊载的原创作品,其版权归属本人所有。

  2. 任何传统媒体、商业公司或其他网站未经本人的授权许可,不得擅自从本人转载、转贴或者以任何其他方式复制、使用上述作品。

  3. 传统媒体、商业公司或其他网站对上述作品的任何使用,均须事先与本人联系。

  4. 对于侵犯本人的合法权益的公司、媒体、网站和人员,本人聘请的律师受本人的委托,将采取必要的措施,通过包括法律诉讼在内的途径来维护本人的合法权益。

特此声明,敬请合作。