阿亮随手录-SpringBoot启动流程、三级缓存要求、BeanFactory与FactoryBean、AutoWired与Resource、不推荐字段注入

记录一下学习的时候研究的一些小问题

SpringBoot 启动流程

  1. 执行 SpringApplication.run()

入口方法,启动整个程序。

  1. 创建 SpringApplication 对象

做一些初始化:

  • 判断是不是 Web 环境

  • 加载所有初始化器、监听器

  1. 启动监听、发布启动事件

发布 ApplicationStartingEvent,开始启动。

  1. 准备环境(Environment)

加载配置:application.yml、bootstrap.yml、系统变量、命令行参数等。

  1. 创建 Spring 容器(ApplicationContext)

根据环境创建:

  • Web 环境:创建 AnnotationConfigServletWebServerApplicationContext

  • 非 Web:创建 AnnotationConfigApplicationContext

  1. refresh 容器(核心)

执行 refresh() 方法:

  • 扫描 Bean

  • 加载自动配置类

  • 初始化所有单例 Bean

  • 完成 AOP、代理、依赖注入

  1. 启动 Web 服务器(Tomcat / Jetty)

如果是 Web 应用,在 refresh 过程中启动内置服务器。

  1. 发布启动完成事件,启动成功

执行 runners(ApplicationRunner、CommandLineRunner),启动完成。

也就是

  1. 调用 SpringApplication.run() 进入启动流程。

  2. 准备环境,加载配置文件。

  3. 创建并初始化 Spring 容器。

  4. 执行 refresh() 扫描、加载、初始化所有 Bean。

  5. 启动 Tomcat 等服务器,发布启动完成事件。

为什么三级缓存要求单例并且不能是构造函数注入

Spring 的循环依赖是通过三级缓存来解决的,

但是它有两个要求:不能使用构造函数注入,并且必须是单例 Bean。

首先说一下原理:

三级缓存的核心思路,就是提前暴露一个已经实例化、但是还没初始化的半成品 Bean,

用这个半成品去打破循环引用的链条。

那为什么不能用构造函数注入?

因为构造函数是在实例化阶段就需要依赖对象,

这个时候 Bean 连对象都还没创建出来,根本没有半成品可以暴露,

所以没办法打破循环,会直接报错。

然后为什么必须是单例?

因为单例 Bean 在整个容器里只有一个实例,

我们才能把这唯一的半成品提前暴露出去。

如果不是单例,每次获取都会创建新对象,

就没有一个固定的、唯一的半成品可以用来打破循环,

最终会无限创建对象,导致循环依赖无法解决。

BeanFactory 和 FactoryBean 的区别

BeanFactory 是 Spring 的顶层基础接口,它负责创建、管理 Bean 的整个生命周期。

我们可以通过 BeanFactory 来获取 Bean、查询 Bean 的信息和状态。

像我们常用的 ApplicationContext,它本身就是 BeanFactory 的一个扩展实现。

FactoryBean 是一个特殊的 Bean 接口。

如果一个类实现了 FactoryBean,Spring 在创建的时候,不会直接返回这个类本身的实例,

而是会去调用它里面的 getObject() 方法,用我们自己写的逻辑去创建一个复杂的对象,再交给 IOC 容器管理。

所以它主要用来自定义创建一些初始化比较复杂的 Bean。

@Autowired 与 @Resource 区别

  1. 来源不同
  • @Autowired 是 Spring 提供的注解,切换 IOC 容器需要改代码。

  • @Resource 是 JDK 标准注解,容器迁移不用改代码。

  1. 查找顺序不同
  • @Autowired :先按类型(byType)查找,类型找到多个,再按字段名匹配。

  • @Resource :先按名称(byName)查找,名称找不到,再按类型查找。

  1. 使用位置不同
  • @Autowired 可以用在:字段、set 方法、构造器。

  • @Resource 只能用在:字段、set 方法,不能用在构造器。

  1. 多 Bean 冲突处理
  • @Autowired 按类型找到多个 Bean,会自动按字段名匹配,匹配失败报错,可用 @Qualifier 指定 Bean 名称。

  • @Resource 先按名称匹配,名称找不到再按类型,类型找到多个也会报错,可直接用 name 属性或 @Qualifier 指定。

  1. 是否允许依赖不存在
  • @Autowired(required = false) :允许依赖不存在,值为 null。

  • @Resource 没有 required 属性,找不到 Bean 直接抛异常。

Spring 为什么不推荐使用字段注入,更推荐构造器注入

Spring 不推荐使用字段注入,更推荐构造器注入,原因主要有这几点:

  1. 更容易发现是否违反单一职责原则

字段注入时,依赖加得再多,代码看起来都很清爽,不容易察觉类太臃肿、职责太多。

换成构造器注入,参数一多就会显得很臃肿,能倒逼我们去拆分类,更符合设计原则。

  1. 单元测试更方便,不强依赖 Spring 容器

字段注入必须启动 Spring 容器才能赋值;

构造器注入可以直接 new 对象,手动传入 Mock 依赖,不用启动容器就能做单元测试。

  1. 可以用 final,保证不可变、更安全

字段注入不能用 final 修饰;

构造器注入可以声明为 private final ,依赖不可变、线程更安全。

  1. 避免初始化顺序导致的空指针 NPE

对象初始化顺序:

类加载 → 静态代码块 → 构造方法执行 → 才进行 @Autowired 字段注入。

如果在构造方法里使用字段注入的对象,此时依赖还没注入,直接空指针。

构造器注入在创建对象时就把依赖传进来,从根源避免 NPE。

  1. 循环依赖能更早暴露

字段注入的循环依赖,Spring 靠三级缓存可以勉强运行,运行时才可能出问题;

构造器注入的循环依赖,项目启动时直接报错,更早发现、更早解决。

  1. 封装性更好,不破坏封装

字段注入相当于直接操作类内部字段,破坏封装性;

构造器注入只通过构造方法传入依赖,类的内部状态对外不可见,更符合面向对象封装。

相关推荐
rOuN STAT2 小时前
MySQL:基础操作(增删查改)
java
→长歌2 小时前
2026Java面试30题精解
java·python·面试
SHoM SSER2 小时前
Spring Boot性能提升的核武器,速度提升500%!
java·spring boot·后端
weixin_425023002 小时前
Spring Boot 2.7 + JDK8 集成 Knife4j 4.1.0 教程(仅展示带注解接口)
java·spring boot·后端
追风林2 小时前
arthas 插件 使用中文
java
rleS IONS2 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端
AI浩2 小时前
别卷 Prompt 了,2026 年 AI 工程的新战场是 Harness
java·人工智能·prompt
IT从业者张某某2 小时前
Dockerfile详解
java·开发语言
WZTTMoon2 小时前
Spring Prototype Bean的四种正确使用方式
spring·原型模式
BPM_宏天低代码2 小时前
【宏天源码】CRM系统的通信中心:邮件/短信/企微消息集成
java·spring boot·freemarker·crm系统