手写Spring核心 -- 实现Bean的定义,注册和获取

Posted by klaus_turbo on 2022-09-07
Spring

前言

在上一篇文章中,我们通过最简单直白的方式,展示了作为一个 bean 容器的基本原理,从最基本的定义,注册,获取三个步骤去展示。通过我们自己定义的 BeanFactory 定义我们的 UserService,以及通过自定义容器去实例化我们的 Bean 等等。但是功能实在是过于粗糙,所以在这一章节我们要去逐步的完善我们的 Spring 容器,实现 bean 容器关于 Bean 对象的注册和获取。

一点思考

我们在上一篇文章中提到了关于 Spring 容器解决循环依赖最基本的一个点就是 Spring 将一个 Bean 进行拆分,通过一系列的属性定义(也就是类信息)去描述这个 Bean 是什么样子的,通过拆分之后,将类信息塞入一个叫做 BeanDefinition 的对象中去。既然如此,那我们就不能像我们前面实现注册那样子,直接把一个 Bean 的 Object 塞入我们的 BeanDefinition 中, 而应该使用的 Object 为 Class 去描述我们的Bean。 接下来需要做的就是在获取 Bean 对象的时候去处理它的实例化以及处理单例对象了。
大概流程如下:

spring_registery_get.png

看 Spring 源码的时候,你或许会发现很多以 BeanFactory 命名结尾的类,像 AbstractBeanFactory,AbstractAutowireCapableBeanFactory等,通过查看类关系图,他们其实都是 BeanFactory的子类,都是对于 BeanFactory 基本方法的实现,或者是在基础功能上进行扩展。所以我们首先要定义的是一个基类:BeanFactory。有一点要永远记住,Spring 容器的最基础的永远是【定义】-【注册】-【获取】这三个,离开了这个主流程再花里胡哨的功能都是瞎白话。当然,就像我们前面一章写的那样子,光有这个主流程肯定是不够的,需要让我们的容器逐步的生产化。那这个过程就是扩展,扩展再扩展。这边就慢慢开始涉及到一些设计模式的东西了,像基础的工厂,模版,策略等都会在实现的过程中被大量的应用。

实践

基本概念有了,那就是完善主流程,并提供相当的扩展能力。

我们先将先前的 BeanDefinition 重新定义,用 Class 替换之前的 Object:

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

private Class beanClass;

public BeanDefinition(Class beanClass) {
this.beanClass = beanClass;
}

public Class getBeanClass() {
return beanClass;
}

public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
}

然后我们定义一个工厂:BeanFactory,并提供最基础的 Bean 的获取方法:getBean(String name):

1
2
3
4
5
public interface BeanFactory {

Object getBean(String name) throws BeansException;

}

这边我们想一下,最基础的获取方法 getBean 有了,那我们需要在此基础上对其进行扩展诸如获取某一类型的 Bean,或者对一些 Bean 进行配置的功能的时候怎么办?有的同学可能就想到了模版方法,我们可以制定一个模版,在后续实现的类需要 getBean 这个方法的时候可以直接使用,而不需要再开发了。

所以我们需要先实现我们的 BeanFactory ,同时把可自定义的一些功能开放出去,我们就新建一个叫做 AbstractBeanFactory 的类:

1
2
3
4
5
6
7
public abstract class AbstractBeanFactory implements BeanFactory {

@Override
public Object getBean(String name) throws BeansException {

}
}

类定义好了,那我们怎么去实现它呢?

首先我们要做的事情就是从一个地方,也就是我们的缓存中根据名称找出我们的 bean ,若 bean 不存在,我们就去实例化它。其实这类操作有一个专有名称,叫做 依赖查找。(提一点,这里面会有一个坑,从我们上述的流程中可以看到,当进行依赖查找的时候,若查找的 bean 不存在的,则会去触发 bean 的实例化,其结果很有可能会导致一些 bean 初始化不完全,或者直接就是出现 NPE 异常。这边就不做展开了,后续有机会会分享相关案例)

既然我们要获取缓存中的 bean,那么我们就需要有一个地方专门的去存我们的 bean,对于已有的 bean 都需要从这个统一的地方去存取。我们知道,对于 Spring 中的 bean 是有 单例 原型 之分的,限于篇幅我们只讨论 单例 这种情况,当然原型相比单例就简单的多的多。所以我们先定义一个用于缓存我们实例化好的 bean 的地方 – SingletonBeanRegistry:

1
2
3
4
5
public interface SingletonBeanRegistry {

Object getSingleton(String beanName);

}

然后定义一个 DefaultSingletonBeanRegistry 来实现它:

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

private final Map<String, Object> singletonObjects = new HashMap<>();

@Override
public Object getSingleton(String beanName) {
return singletonObjects.get(beanName);
}

protected void addSingleton(String beanName, Object singletonObject) {
singletonObjects.put(beanName, singletonObject);
}

}

这样我们就知道了,缓存单例 bean 怎么缓存,在哪缓存。

那当我们查找的单例 bean 不存在的时候我们需要去初始化它。你还记得我们初始化 bean 应该用什么呢?

对,就是我们的 BeanDefinition 。

与此同时,当我们对于 bean 的初始化需要做一些干涉,或者说是定制的时候,那我们就需要在我们初始化 bean 的过程中给予足够的想象空间。所以我们的 AbstractBeanFactory 就理所应当的实现成了如下的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

@Override
public Object getBean(String name) throws BeansException {
Object bean = getSingleton(name);
if (bean != null) {
return bean;
}

BeanDefinition beanDefinition = getBeanDefinition(name);
return createBean(name, beanDefinition);
}

protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;

protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException;

}

这样,一个相对典型的模版模式的模版就完成了。

模版有了,我们就需要这么一个类,用于装配,用于实例化我们的 bean,来完成【BeanDefinition】-> 【Bean实例】的这个 createBean 的过程。我们知道,我们的 BeanDefinition 是用来描述一个实实在在的 bean 的,所以在 BeanDefinition 中存放的是具体的 bean 的属性信息。在这里 bean 就像一辆汽车,被拆分成了若干的零件。在我们要实现的 createBean 方法中要做的事情就是把一堆汽车零件组装成我们的汽车。所以也就可以理解,为什么我们下面实现 createBean 方法的类叫做 AbstractAutowireCapableBeanFactory 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException {
Object bean;
try {
bean = beanDefinition.getBeanClass().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new BeansException("Instantiation of bean failed", e);
}

addSingleton(beanName, bean);
return bean;
}

}

你可以看到,我们在实现的部分只是简单的用 newInstance 去实例化我们的 bean ,但在实际的应用中,bean 的构造函数很有可能是有入参的,这里卖一个关子,你可以想一下怎么去解决。

在我们处理完 bean 的实例化之后,我们调用了 SingletonBeanRegistry 的 addSingleton 方法,把我们实例化好的 bean 放入缓存之中。

createBean 方法是被实现了,那我们的 getBeanDefinition 怎么去实现呢?其实也简单,这就是我们一开始的时候提到的主流程中的【注册】这一步骤,我们定义一个类,用于注册我们的 BeanDefinition,名字就叫做 BeanDefinitionRegistry :

1
2
3
4
5
public interface BeanDefinitionRegistry {

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

}

好了,我们主流程中的【注册】与【获取】都有了,但你也看到了,我们之前使用的都是抽象类,没有办法实例化使用,而且【注册】与【获取】的流程是分开的。所以我们就需要最后一个角色,将他们串联起来,同时作为一个正式的 BeanFactory 将它提供出去,我们称之为 DefaultListableBeanFactory。接下来看他的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {

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

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}

@Override
public BeanDefinition getBeanDefinition(String beanName) throws BeansException {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new BeansException("No bean named '" + beanName + "' is defined");
}
return beanDefinition;
}
}

在 Spring 源码中,DefaultListableBeanFactory 也是一个非常核心的类,我们通过各种继承让我们最终提供出去的 DefaultListableBeanFactory 拥有了一连串的功能。同时通过实现 BeanDefinitionRegistry 让其有了注册 BeanDefinition 的能力。这样,整个流程就形成了一个闭环,从【注册】到【获取】一步步的已经有了我们想要的样子。

那还是老规矩,实现了之后我们看一下怎么去使用,看看是不是真的和 Spring 那样,可以通过我们简化版的依赖查找的实现找到我们的 bean 并使用它。

还是定义一个 UserService:

1
2
3
4
5
6
7
public class UserService {

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

}

然后写一个测试demo:

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

// 2.定义 BeanDefinition
BeanDefinition beanDefinition = new BeanDefinition(UserService.class);

// 3.注册 bean
beanFactory.registerBeanDefinition("userService", beanDefinition);

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

}

运行一下:

spring02_调试结果.png

OK ,成功的通过我们的 DefaultListableBeanFactory 实例化了 UserService,并完成了调用。

那前面的关子怎么解决

BeanFactory 我们是完成了,但你还记得前面我们说的一个关子吗?我们在实际的 bean 的使用过程中,构造函数是很有可能是有入参的,那这样子在我们这个 createBean 方法中就没有办法去实例化我们的 bean,那我们应该怎么去处理呢?

既然我们的在 BeanDefinition 中使用了 Class 对象去描述我们的 bean,那其实也不难想到,我们可以通过 Class 对象获取我们这个 bean 的构造函数,并根据构造函数的参数是否大于一个来决定怎么去实例化我们的 bean。

实例化的方式其实可以想到的有两种:

  • JDK
  • CGLIB

所以我们在实例化方式这边可以用一个策略模式去实现,我们先定义一个用于实例化的接口:

1
2
3
4
5
public interface InstantiationStrategy {

Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException;

}

然后就是分别按照 JDK 和 CGLIB 的方式去实现 InstantiationStrategy,先是基础的 JDK 方式,我们命名为 SimpleInstantiationStrategy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SimpleInstantiationStrategy implements InstantiationStrategy {

@Override
public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
Class clazz = beanDefinition.getBeanClass();
try {
if (null != ctor) {
return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
} else {
return clazz.getDeclaredConstructor().newInstance();
}
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new BeansException("Failed to instantiate [" + clazz.getName() + "]", e);
}
}

}

然后是实现基于 CGLIB 的部分,命名为:CglibSubclassingInstantiationStrategy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {

@Override
public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor ctor, Object[] args) throws BeansException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(beanDefinition.getBeanClass());
enhancer.setCallback(new NoOp() {
@Override
public int hashCode() {
return super.hashCode();
}
});
if (null == ctor) return enhancer.create();
return enhancer.create(ctor.getParameterTypes(), args);
}

}

这边的实现方式与 Spring 源码的继承关系有一点出入,源码中 CglibSubclassingInstantiationStrategy 与 SimpleInstantiationStrategy 是父子关系,CglibSubclassingInstantiationStrategy 直接继承自 SimpleInstantiationStrategy,因为在 Spring 源码中 SimpleInstantiationStrategy 的主要作用是对于单个简单的 bean 进行实例化,像 方法注入 在 SimpleInstantiationStrategy 并没有得到处理和实现,而是由 CglibSubclassingInstantiationStrategy 去重写了 SimpleInstantiationStrategy 的 instantiateWithMethodInjection 方法,并在 CglibSubclassingInstantiationStrategy 内部处理了当要被实例化的 bean 内部使用了 MethodInjection 的情况。

当然,现阶段由于我们的 BeanDefinition 还不是很完善,对于其中的 构造函数 等都没有定义,所以我们就直接重载 getBean 方法,添加一个参数列表:

1
2
3
4
5
6
7
public interface BeanFactory {

Object getBean(String name) throws BeansException;

Object getBean(String name, Object... args) throws BeansException;

}

然后在 AbstractBeanFactory 中实现,同时我们的 createBean 方法也要添加一个代表 bean 参数列表的入参:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null);
}

@Override
public Object getBean(String name, Object... args) throws BeansException {
return doGetBean(name, args);
}

// 提取公共内容 doGetBean 内部实现
protected <T> T doGetBean(final String name, final Object[] args) {
Object bean = getSingleton(name);
if (bean != null) {
return (T) bean;
}

BeanDefinition beanDefinition = getBeanDefinition(name);
return (T) createBean(name, beanDefinition, args);
}

protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;

protected abstract Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException;

}

在完成了一系列的修改之后,我们来看看现在的 AbstractAutowireCapableBeanFactory 内部的 createBean 方法变成了什么样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
Object bean = null;
try {
bean = createBeanInstance(beanDefinition, beanName, args);
} catch (Exception e) {
throw new BeansException("Instantiation of bean failed", e);
}

addSingleton(beanName, bean);
return bean;
}

// 将代码提取出来
protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {
Constructor constructorToUse = null;
Class<?> beanClass = beanDefinition.getBeanClass();
Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
for (Constructor ctor : declaredConstructors) {
if (null != args && ctor.getParameterTypes().length == args.length) {
constructorToUse = ctor;
break;
}
}
return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);
}

public InstantiationStrategy getInstantiationStrategy() {
return instantiationStrategy;
}

public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
this.instantiationStrategy = instantiationStrategy;
}

}

我们再来测试一下有参构造的 UserService ,改成有参构造:

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

private String name;

public UserService() {
}

public UserService(String name) {
this.name = name;
}

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

再通过 DefaultListableBeanFactory 来执行一遍 定义 - 注册 - 获取 的流程:

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

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

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

看一下控制台输出结果:

spring02_调试结果02.png

好了,现在我们的 BeanFactory 能够处理有参构造函数的 bean 的实例化了。

到这我们的这一章节就结束了,希望对你有所帮助。在下一章节我将继续分析实现如何去处理 bean 属性的注入 和 依赖对象,敬请期待。

总结

相对前面一章对 Spring Bean 容器的简单概念的实现,我们这里完善了更多的功能。但你可以发现在整个实现的过程中,类的关系逐渐变的复杂,并且由于考虑了一些扩展方面的能力,运用设计模式,使得类的数量一下子多了出来。Spring 对于设计模式的有效运用真的让人叹为观止,但正是这些复杂的设计,使得 Spring 本身十分的强大。同时 Spring 对于类的功能职责和作用范围做的非常的好,有大量的 DDD 概念的运用,它很清楚什么类做什么事情。而且你也看到了,在我们 DefaultListableBeanFactory 出现之前,几乎大部分都是依靠接口和抽象类在做交互,完美的诠释了那句话:“底层抽象,高层实现”,也正是这样,使得其底层的功能十分的稳健。

对于想去看源码的同学,我是建议你先不要关注具体的实现,而应该把重点放在类之间的职责和关系上面,这个是我看的下去的基础。当然我们这只是简化的版本,在源码处很有可能会使人无从下手,debug 都不知道从哪开始。

最后,作为一个程序员,我觉得我们要记住:代码实现只是最后的落地结果,重要的是设计上的思考,那才是最有价值的地方。

1
2
3
4
5
6
7
8
9
10
11
本人关于图片作品版权的声明:

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

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

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

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

特此声明,敬请合作。