《spring如此简单》第四节--IOC思想的实现,spring启动后发生了什么

了解spring框架,就需要知道spring项目启动后,框架里发生了什么事情。而springboot是spring的高级封装版,就按照springboot为例来讲。

arduino 复制代码
SpringApplication.run(Application.class, args)

就是要理解这句话到底干了什么。

容器创建之前

  1. 构建 SpringApplication 实例。可以把SpringApplication视为是一个启动调度总指挥。

它并非容器本身,而是统筹初始化到运行全流程。

它的核心功能为:

  1. 保存启动类,也就是Application,将其作为包扫描、自动配置的起点。
  2. 判断应用类型。
  • SERVLET:Web 项目(带 Tomcat)
  • REACTIVE:响应式 WebFlux
  • NONE:普通 Java 项目(无服务器)
  1. 加载所有初始化器ApplicationContextInitializer,从 META-INF/spring.factoriesSPI 加载。初始化器里定义的流程,将会在容器创建后,实行refresh()前执行,可以对容器做一些预处理或者配置修改。
  2. 加载所有监听器(ApplicationListener),监听启动的特定节点。starting、environment、context、ready 等
  3. 推断出 main 方法所在类,保证启动正确性
  4. 创建运行监听器集合(SpringApplicationRunListeners),并且发布事件ApplicationStartingEvent
  5. 解析命令行参数,构建运行环境,然后发布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这个关键事件。

  1. 加载环境后,会执行ApplicationContextInitializer,可能会对环境做一些修改。
  2. 最后的准备阶段,打印横幅
markdown 复制代码
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

到这里,我们才开始要准备构建容器

容器构建中

  1. 根据应用类型,实例化对应 ApplicationContext IoC 容器。

类型如何判断呢,Spring 会检查 classpath 里有没有对应的类,自动判断类型:

  • Servlet Web 环境 (最常用)存在:ServletSpring MVC→ 创建 AnnotationConfigServletWebServerApplicationContext
  • Reactive Web 环境 存在:WebFlux→ 创建 AnnotationConfigReactiveWebServerApplicationContext
  • **普通单机应用(非 Web)**以上都没有→ 创建 AnnotationConfigApplicationContext

一句话:你项目里有什么包,Spring 就给你造什么容器!当然这也是约定大于配置思想的体现。

  1. 容器刚new 出来,做一些初始化。
  • 把已经准备好的 Environment 设置给容器
ini 复制代码
context.setEnvironment(environment);
  • 创建容器的底部工厂DefaultListableBeanFactory,真正在创建bean的,是它
  • 初始化容器的工具组件。资源加载器、类加载器、注解解析器、事件广播器
  • 标记容器状态:已创建,未刷新
  1. 到这里,开始执行重头戏,耳熟能详的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容器刷新完成事件。

容器构建完成后

主角容器已经就位,接下来就是正式的工作内容了。

  1. 启动内嵌 Tomcat Web 服务器。

为什么tomcat在容器refresh()之后呢?因为其需要的DispatcherServletFilterServlet 等bean在容器准备好后才创建好。

启动tomcat的是ServletWebServerApplicationContext。

  • 创建 Tomcat 实例
  • 加载上下文、连接器
  • 注册 DispatcherServlet
  • 注册过滤器、监听器
  • 绑定端口(8080)
  • 启动 Tomcat 线程,开始监听请求

此刻,springboot工程正式启动,剩下的我们都知道,是springmvc的事情了。

这就是IOC思想的完整实现,啃过了这个骨头,后面的内容就是一马平川了,下一篇就是spring的另一大思想------AOP,再理解了它,基本就理解了整个spring的核心。

相关推荐
木雷坞1 小时前
Home Assistant 升级翻车:一套 Docker Compose 回滚清单
后端
2301_800895101 小时前
计算机网络保研面试(自用版h)
计算机网络·面试
SamDeepThinking1 小时前
面试官问Bean线程安全,你该从架构角度回答
java·后端·面试
用户713874229001 小时前
git fsck 深度解析 Git 仓库的体检医生
后端
风度前端1 小时前
阿里云宝塔面板部署https证书
linux·后端·https
还是鼠鼠1 小时前
AI掘金头条新闻系统 (Toutiao News)-相关推荐
后端·python·mysql·fastapi·web
AskHarries1 小时前
OpenClaw 是什么?为什么它不是普通 AI Agent
人工智能·后端·程序员
Tsuki_tl1 小时前
【总结】Java的线程状态
java·后端·面试·多线程·并发编程·线程状态
xiaoxue..2 小时前
Node.js 笔试题讲解
后端·面试·node.js