了解spring框架,就需要知道spring项目启动后,框架里发生了什么事情。而springboot是spring的高级封装版,就按照springboot为例来讲。
arduino
SpringApplication.run(Application.class, args)
就是要理解这句话到底干了什么。
容器创建之前
- 构建 SpringApplication 实例。可以把SpringApplication视为是一个启动调度总指挥。
它并非容器本身,而是统筹初始化到运行全流程。
它的核心功能为:
- 保存启动类,也就是Application,将其作为包扫描、自动配置的起点。
- 判断应用类型。
SERVLET:Web 项目(带 Tomcat)REACTIVE:响应式 WebFluxNONE:普通 Java 项目(无服务器)
- 加载所有初始化器ApplicationContextInitializer,从
META-INF/spring.factories或SPI加载。初始化器里定义的流程,将会在容器创建后,实行refresh()前执行,可以对容器做一些预处理或者配置修改。 - 加载所有监听器(ApplicationListener),监听启动的特定节点。starting、environment、context、ready 等
- 推断出 main 方法所在类,保证启动正确性
- 创建运行监听器集合(SpringApplicationRunListeners),并且发布事件ApplicationStartingEvent
- 解析命令行参数,构建运行环境,然后发布ApplicationEnvironmentPreparedEvent事件
将 java -jar app.jar --spring.profiles.active=dev --server.port=8081这个启动命令解析,封装为DefaultApplicationArguments。这也是为什么参数里面有args。启动项里的配置优先级最高,会覆盖配置文件、环境变量。
然后,构建运行环境Environment,这是spring项目的统一配置入口,管理所有的配置。
- 命令行参数(最高优先级)
- 系统环境变量
- JVM 系统参数
- application.yml / application.properties
- 激活的 profile(dev/test/prod)

在构建完毕后,会发布ApplicationEnvironmentPreparedEvent这个关键事件。
- 加载环境后,会执行ApplicationContextInitializer,可能会对环境做一些修改。
- 最后的准备阶段,打印横幅
markdown
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
到这里,我们才开始要准备构建容器
容器构建中
- 根据应用类型,实例化对应 ApplicationContext IoC 容器。
类型如何判断呢,Spring 会检查 classpath 里有没有对应的类,自动判断类型:
- Servlet Web 环境 (最常用)存在:
Servlet、Spring MVC→ 创建 AnnotationConfigServletWebServerApplicationContext - Reactive Web 环境 存在:
WebFlux→ 创建 AnnotationConfigReactiveWebServerApplicationContext - **普通单机应用(非 Web)**以上都没有→ 创建 AnnotationConfigApplicationContext
一句话:你项目里有什么包,Spring 就给你造什么容器!当然这也是约定大于配置思想的体现。
- 容器刚new 出来,做一些初始化。
- 把已经准备好的 Environment 设置给容器
ini
context.setEnvironment(environment);
- 创建容器的底部工厂DefaultListableBeanFactory,真正在创建bean的,是它
- 初始化容器的工具组件。资源加载器、类加载器、注解解析器、事件广播器
- 标记容器状态:
已创建,未刷新
- 到这里,开始执行重头戏,耳熟能详的refresh()开始启动
- 准备刷新环境 prepareRefresh。
标记容器为「正在刷新」,加锁防并发重复启动,修改容器运行状态标识。
加载系统变量、JVM 参数、读取application配置、激活当前环境(dev/prod/test)。
清空容器旧名称缓存、属性缓存、资源缓存,初始化全新运行上下文数据。
预留扩展点,可自定义刷新前置处理
- 刷新 Bean 工厂、加载 Bean 定义:obtainFreshBeanFactory
销毁旧工厂 ,新建干净的DefaultListableBeanFactory
设置工厂基础属性:类加载器、表达式解析器、依赖注入规则
执行包扫描 ,遍历启动类所辖包,识别组件注解。这里就是大名鼎鼎的@ComponentScan包扫描机制。生成生成BeanDefinition蓝图。
将全部 Bean 定义批量注册进 BeanDefinitionRegistry。
!!从这里开始,applicationContext和Bean的命运开始交织,可以看出,这个步骤就是对应前一节内容中,Bean生命周期的第一步。
- 执行 BeanFactory 后置处理器。这是一个预留的方法,可以在还未创建bean的时候,对蓝图BeanDefinition做一些操作。
java
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
它的接口长这样,意味着你可以获取工厂。然后,获取bean蓝图,对其进行增删改,甚至自己注册。
- 注册所有 Bean 后置处理器registerBeanPostProcessors。从BeanDefinition里找出所有的BeanPostProcessor类型,提前实例化它们,注册到Bean工程的处理器列表。
ini
beanFactory.addBeanPostProcessor(postProcessor);
BeanPostProcessor这个接口是老熟人了
scss
// 初始化之前执行
postProcessBeforeInitialization(bean, beanName)
// 初始化之后执行
postProcessAfterInitialization(bean, beanName)
没错,这就是bean的生命周期中,在其初始化前后执行的方法。
- 初始化内置消息源(国际化)
- 初始化事件广播器,后续进行广播事件的转发(观察者模式)
- 注册自定义事件监听器,把所有 ApplicationListener 监听器,注册到事件广播器里。
- 实例化所有非懒加载单例 Bean。
冻结 Bean 定义,不再允许修改图纸
遍历所有 BeanDefinition,筛选非懒加载、单例Bean
然后进行的就是bean生命周期的,实例化半成品,依赖注入,前置处理,初始化,后置处理,塞进单例池这一系列操作了。
- 大功告成,发布ContextRefreshedEvent容器刷新完成事件。
容器构建完成后
主角容器已经就位,接下来就是正式的工作内容了。
- 启动内嵌 Tomcat Web 服务器。
为什么tomcat在容器refresh()之后呢?因为其需要的DispatcherServlet、Filter、Servlet 等bean在容器准备好后才创建好。
启动tomcat的是ServletWebServerApplicationContext。
- 创建 Tomcat 实例
- 加载上下文、连接器
- 注册
DispatcherServlet - 注册过滤器、监听器
- 绑定端口(8080)
- 启动 Tomcat 线程,开始监听请求
此刻,springboot工程正式启动,剩下的我们都知道,是springmvc的事情了。
这就是IOC思想的完整实现,啃过了这个骨头,后面的内容就是一马平川了,下一篇就是spring的另一大思想------AOP,再理解了它,基本就理解了整个spring的核心。