续言


要说 Spring 框架中哪个概念最出名的话,非 Bean 莫属了,由它延伸而出的依赖注入和控制反转也是设计模式中的重要概念,其简洁和高效的特性被开发者津津乐道,各个框架都纷纷效仿,今天我们也给 winter 中添加这么一个特性。

转载随意,文章会持续修订,请注明来源地址:https://zhenbianshu.github.io

Bean


Bean

Bean 的实质没什么特殊的,它本质上是一类特殊的对象,只不过它的生命周期不同于一般对象的朝生暮死,而是在服务初始化、JVM 启动时开始,在服务结束 JVM 销毁时结束。除此之外,由于其在整个虚拟机中可见,且维护成本较高,它一般是单例存在的。

基于这些特性,Bean 的存在使框架更为高效。

  • 在项目启动时初始化,使用时不再需要初始化,大大加快了使用 bean 对象时的效率,
  • bean 通过框架进行统一管理,代码中少了很多 new 某个 service 对象的重复代码。
  • 减少了很多对象使用后抛弃的情况,大大减轻了垃圾回收器的压力。
  • bean 的依赖关系通过框架进行管理,我们也不必再在创建对象时 set 各种 property,更不必处理链式依赖时,各种 new 和 set。
  • bean 保存到框架中后,我们对某些类进行统一操作更简单了,也让我们统一代理类的行为成为了可能。

Bean 也是一种对象,而对象并不需要也没办法再进行包装,所以 Spring 中并没有包装 Bean 的定义。而上节中我们说到,哪些类需要实例化 Bean 是需要一个标志的,我们还需要添加一个 @Bean 注解,类似于 Spring 中的 @Service @Component 等。

beanFactory

Bean 在虚拟机中通过 BeanFactory 维护,顾名思义,我们可以理解它就是工厂模式中创建对象的工厂,我们也能从这种 Bean 工厂内获取到对应的 Bean。

我们想像中的工厂是生产某类东西的地方,而 BeanFactory 在 Spring 中是一个接口,其仅仅定义了一系列获取 Bean 的接口方法,并没有限制这些 Bean 是从哪来的。通过 Bean 工厂,我们可以通过类型、名字获取到对应的 Bean,也可以判断某个 bean 是否在工厂内。

由于 Spring 特性丰富,还有对 Bean 别名定义的接口方法,不过对我们这个简单的框架来说,实现通过名字和类型获取 Bean 就够了。

public interface BeanFactory {
    Object getBean(String beanName);

    Object getBean(Class<?> cls);
}

bean 保存和查找

另一个重点是 bean 的保存,我们的要求是 bean 在全局内可见,而且可查找,这就需要我们有一个全局容器能够存储各个类,这种情况下,静态属性是最好的选择。 除此之个,容器的类型我们要选择 ConcurrentHashMap,用以避免 bean 操作时的冲突问题,key 是 bean 的名字,我们参照 Spring 中 bean 名字的定义,使用 类名首字母小写

又由于我们还需要通过类型查找 bean,还需要添加一个类名到 Bean 的映射。

IOC 和 DI


依赖注入和控制反转经常在一块被提起,它们是设计模式中非常重要的概念,但是很多人对他们的认识可能不够正确,认为依赖注入就是控制反转,它们之间没什么不一样。

我们来重新认识一下这两个概念。 首先是控制反转,它的英文全称为 Inversion of Control,缩写为IoC,它是面向对象编程中的一种设计原则,也是一种思想,用来降低代码之间的耦合度。反转很容易理解就是倒过来了,但控制呢,有点模糊,其实它是指对它依赖的对象,通常是属性的控制。总的来说就是某个对象对他属性的控制被反转了。

那么怎么反转的呢,就不得不提到依赖注入,Dependency Injection,简称DI,它是实现控制反转的一种方式,它就较容易理解一些,采用依赖注入技术,一个类A里有一个类型为B的属性,只需要在A的代码定义一个私有的B对象,实例化类A时不需要我们直接 new 来获得这个对象,而是通过容器控制程序将B对象在new出来并注入到A类里的引用中。

我们用例子来详细地说一下这两个概念。 首先在我们的普通代码里,类 A 里有一个类型为 X 的属性,那么我们在创建A对象时需要先创建一个 X对象,然后再把这个 X对象 set 到 A对象里。我们会发现,A 依赖 X,而解决这种依赖的方式是由 A 主动创建一个X,这种情况,A 对它的依赖的控制方式就是依赖正转。

控制反转情况下,除了 A 和 X 之外,还必须有一个第三方的容器存在,使用它来调控系统内所有对象的外界实体,在Spring时就是 BeanFactory 了。

在 A 类通过属性引用声明它依赖X之后, BeanFactory 就获取了这个信息,然后在创建 A对象之前,就由 BeanFactory 创建好了一个 X 对象,并将这个 X 对象 SET 么 A 对象的属性里。这跟刚才不一样的地方在于 A 对象没法真正控制自己依赖的对象,而是被别人控制了,这就是控制反转了。而这个控制反转的方式,就是把 X 对象 SET 到 A 对象属性里这个动作,就是依赖注入。

除了依赖注入外,使用依赖查找也能实现控制反转,也就是等 A 对象在使用 X 属性时再从 BeanFactory 里面找一个 X 对象给他用。

简易的 Bean 创建流程


一开始,我们获取到了项目里所有的类定义,也就是 Classes 容器,我们对它们进行循环遍历处理。

先判断要创建这个 Bean 需不需要解决依赖,这个可以看它的属性即可。如果它没有依赖,就可以直接实例化,Bean 的创建我们同样使用上节中用过的反射cls.newInstance() 一行代码即可,创建完成再把它放到准备好的 Bean 工厂内。 而如果这个 Bean 依赖其他 Bean,就需要查一下这个 Bean 在 Bean 工厂内是否存在了,如果存在,也好说,我们从 Bean 工厂取出被依赖 Bean 的引用,用反射 Set 到正在创建的 Bean 里,完成 Bean 的创建。

如果这个 Bean 的依赖在 Bean 工厂内找不到,我们就暂时先放弃创建它,继续创建后面的 Bean,等后面的 Bean 创建完了,我们再从头遍历剩下创建失败的 Bean。

当然,我们得考虑一下异常情况,这种情况就是 Bean 之间相互依赖的问题,由于其实现较为复杂,我们暂时不准备解决这个问题,但是必须得发现这个问题,发现这个问题也很简单,查看一个每次遍历结束后,创建失败的 Bean 有没有少即可,如果创建失败的 Bean 没有少,说明陷入了循环依赖,这时候要抛出异步,退出死循环。

Spring 当然是解决这个问题了,它采用了分步创建和三级缓存的策略,也是十分精妙。

小结


Bean 创建的流程完善后,再对项目进行打包测试,一个简单的 mini spring 框架就完成了,之后就是对它的持续优化和扩展了。

关于本文有什么疑问可以在下面留言交流,如果您觉得本文对您有帮助,欢迎关注我的 微博GitHub 。您也可以在我的 博客REPO 右上角点击 Watch 并选择 Releases only 项来 订阅 我的博客,有新文章发布会第一时间通知您。