Spring 启动过程简析

前情提要

本文不是源码分析,只是分析了一下Spring启动流程,让有兴趣了解这方面的内容同学有一个大体的方向,

Spring 是一种思想,设计方式,而不是一头扎入源代码的世界,所谓“不识庐山真面目,只缘身在此山中“,一开始就进入细节中,很容易在山中迷路的。

观其脉络

Spring 本身是一个“容器”,剥开IOC(依赖注入)和DI(依赖注入)这些”外衣“,Spring的本质就是创建对象,那么问题来了要如何创建一个对象呢?

有过面向对象编码经验的同学都知道,创建对象嘛,不就是new吗,举个栗子:

 User user=new User("张三")

看到这里,你或许会问:“Spring就干了这么简单的一件事情?欺负我没有看源代码?”。

确实,如果只是这么简单的一个对象,或者就几个对象,手动new就可以了,但是,我们的一个项目动辄三五个模块、百八十个服务类(Service\Dao\Controller),这么多对象,加上对象与对象的依赖关系问题,如果都手动创建的话,有点一言难尽。

这个时候有同学或许会说,我可以写对象工厂啊,恭喜你,答对了,Spring也是这么干的,如果对Spring有一些了解的同学会知道Spring的核心接口就是BeanFactory。

与我们手写的创建某一类型或者某种类型的对象的Factory略有不同,Spring的BeanFactory是用于创建符合Spring “规则“的对象,这个规则或者说定义就是BeanDefinition接口。

对象的创建、销毁

正如我们手动new一个对象时,我们需要知道这个对象是否有依赖其他对象,创建完以后是不是还有调用对象的类init方法做一些额外的初始化。Spring在创建对象的时候也需要知道这些信息,而BeanDefinition就描述这些信息的。

由于对象创建的复杂性,某些对象需要单例的,有些对象每次使用都需要重新创建,有些对象在销毁是需要释放资源(例如数据源)等等,不一而足。

可以看到创建对象的需求是复杂多变的,所以Spring的BeanDefinition主要描述对象的依赖信息、初始化方法名称(初始化过程)、销毁对象时需要调用的方法(销毁过程)、Bean scope(决定Bean作用范围,单例\每次创建\在一个http请中\在一个http会话中)、BeanName(对象的唯一标识),BeanType(创建Bean对象的类全名)等信息。

如果把对象创建的过程类比炒菜的话,BeanFactory就是厨房(因为一个厨房可能有多个厨师,不同的菜可以由不同的厨师处理,相同的菜由不同的厨师处理结果可能也会不同),BeanDefinition就是某道菜的具体做法了(一个BeanDefinition描述了具体的一个对象的创建过程,那么一个项目就需要许多的BeanDefinition对象了)。

那么,如果需要提供一份”大餐“,就缺少了这道大餐的菜单了。所以这个时候需要有一个人开始点餐生成这份菜单,交由Spring这个厨房去生产了(如何得到BeanDefinition)。

BeanDefinition 的生成

包扫描配置,这个操作只要用过Spring的同学是在熟悉不过了,如果继续按照上文的“厨房类比”,我们的代码(项目),就是原材料,在配置了包扫描(在SpringBoot中会自动识别)后,“厨房”将会派出一位原材料审核人士ClassPathScanningCandidateComponentProvider,识别出(项目下或依赖的Jar中)符合Spring编码约规(Bean\Component\Service\Controller\Configuration等注解,以及通过Ant path匹配配置的包名)的原材料(类)转换成BeanDefinition(菜谱)。

BeanDefinition的转换依赖于Java的反射,以及开发者在类对象上或者创建Bean的方法上提供的注解(还有xml的配置)。

通过ClassPathScanningCandidateComponentProvider提供的BeanDefinition就是Spring这个专业“厨房”,为你的项目量身定制的菜单和菜谱。菜单说明了项目这道大餐需要制作的“菜”(创建的对象),“菜谱”描述了每道菜制作的步骤,每道菜出菜的顺序(Spring的Order注解和Ordered接口)。

有了菜单和菜谱,厨房就开始给各个厨师(BeanFactory)派活了,很快为你量身定制的大餐就都出锅了(项目启动了)。

当然在做菜的过程中有的时候还会遇到菜品间的依赖问题(对象的依赖),比如:麻婆豆腐要先有豆腐吧,所以先得吧豆腐做出来(这个类比不是特别形象,大家理解到对象是有依赖顺序的问题就好)。

循环依赖问题(不是重点,可以略过)

当Spring 创建对象发现 类似A依赖B,B依赖C,C又依赖A时,这个时候回出现循环依赖的异常(高版本的Spring有增加对循环依赖的优化处理,不过特别复杂或者Spring无法自动处理时,需要开发人员手动处理)。

这个时候可以通过Lazy注解,加在某个类或者字段声明上(具体用法请自行搜索),表明某个Bean是可以延迟初始化的,解决循环依赖的问题。

被Lazy标记的Bean会做延迟初始化,例如这个循环依赖:A->B->C->A->B,如果声明了A延迟初始化,假设先创建B对象,然后穿件C对象,C对象创建发现需要A对象,结果A对象创建
又需要B对象,这个时候在创建A对象的时候会暂停B对象的创建,先把创建到一半的B对象传递给A对象等A对象和C对象都创建成功后,在创建B对象,所以B对象会有一段时间是处于半初始化状态,且由于B对象是吧应用传递给了A对象,由于依赖关系和引用传递的原因A、B、C这三个对象的Bea的scope都必须是单例的。

当然解决循环依赖的问题的最好办法就是尽量不出现循环依赖。

Bean代理,增强、自定义创建过程

当Sprig接管了所有对象的创建后,这个实力强大的“厨房”已经不满足单纯的做菜了,掌控全局的它绝对特殊的菜做加工,赋予这些“菜”特别的能力,比如说:让人吃了就精力充沛的豆腐?

通过在识别特定的注解,例如:Transactional,Aspect等,结合BeanFactoryPostProcessor(BeanFactory工厂“增强”处理)和BeanPostProcessor(Bean对象创建时“增强”处理),让开发者实现声明式的事务处理、AOP编。

另外当一些菜制作比较复杂,需要客人(开发者)配合,Spring 通过支持javax的PostConstruct注解,提供InitializingBean\Lifecycle接口等,让那个开发者可以更自由的处理Bean的创建过程。

更复杂的情况是,某道菜或者某种是“菜是私人定制”的,只有开发者才知道才有这道菜的具体做法(~祖传菜谱)这个Bea要如何创建,那么就需要开发人员提供一个FactoryBean的实现,用来完全描述这类Bean的创建细节。(像不像是在大厨房里加了一个私人厨师,给你提供定制的菜,这样想一想还有点小激动)

配置和环境

其实Spring在启动时是最早加载配置文件的(xxx.properties\xxx.yaml),配置就像是一下"佐料",在“做菜”之前要先准备好。

所有的配置内容会被转换为PropertySource对象,放到Environment中,多个配置文件就是多个
PropertySource对象,同样的EnvironmentPostProcessor,用来增强Spring的环境、配置等。

Environment就是Spring在运作时所依赖的环境,可以看做是Spring这个“厨房”里面的细节,提供锅碗瓢盆等。

事件

一道大餐的制作都是复杂,一个厨房里除了主厨(AbstractAutowireCapableBeanFactory)还会有其他大厨、帮厨(BeanFactoryPostProcessor、BeanPostProcessor)等,为了让所有“人”协同合作,沟通进度,厨房需要一些传话人(ApplicationListener),在整个厨房中传递一些消息(ApplicationEvent),推进厨房的做菜进度。

当然以上只是一个不太形象的类比,ApplicationListener本身是一个发布/订阅的机制,有兴趣的同学可以了解一下观察者模式或者事件机制等等。

“一览众山小”

这样一看Spring的启动过程似乎并不复杂,主要如下分为几个步骤

  1. 通过包扫描获取项目中的配置,并初始化环境。
  2. 通过包扫描得到需要创建的类并转换为BeanDefinition。
  3. 通过1、2步骤得到了“佐料和菜单、菜谱”,开始做菜(创建Bean)。
  4. 重复第三步,直到所有的Bean都被创建完成。
SpringBoot

如果对上文的描述有了比较清晰的认知,那么SpringBoot往简单的说就是在Spring这个"厨房"加了好多厨师、帮厨等,用来做一些常见的菜(基本上的项目都会用的配置、Bean等)。

例如:DataSource\Web\Jpa\Redis等,就不一一细数了。

而且为尽量满足大家的口味(保证不同情境、不同项目的情况),SpringBoot提供了一系列ConditionXx的注解(condition bean本身是Spring的设计,SpringBoot扩展了这个功能)。
简单说condition bean通过一些条件判断和探测来决定是否将某些”厨师“(AutoConfiguration)放入到厨房中(Spring的上下文)。

结语

正所谓”一叶障目,不见泰山”,期望本文能够让各位看官对Spring的整理设计有一个较为清晰的认知,在您想细究Spring的源码实现时不至于在“山中迷了路”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值