续言
要说 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
项来 订阅
我的博客,有新文章发布会第一时间通知您。