记录一下学习的时候研究的一些小问题
SpringBoot 启动流程
- 执行 SpringApplication.run()
入口方法,启动整个程序。
- 创建 SpringApplication 对象
做一些初始化:
判断是不是 Web 环境
加载所有初始化器、监听器
- 启动监听、发布启动事件
发布 ApplicationStartingEvent,开始启动。
- 准备环境(Environment)
加载配置:application.yml、bootstrap.yml、系统变量、命令行参数等。
- 创建 Spring 容器(ApplicationContext)
根据环境创建:
Web 环境:创建 AnnotationConfigServletWebServerApplicationContext
非 Web:创建 AnnotationConfigApplicationContext
- refresh 容器(核心)
执行 refresh() 方法:
扫描 Bean
加载自动配置类
初始化所有单例 Bean
完成 AOP、代理、依赖注入
- 启动 Web 服务器(Tomcat / Jetty)
如果是 Web 应用,在 refresh 过程中启动内置服务器。
- 发布启动完成事件,启动成功
执行 runners(ApplicationRunner、CommandLineRunner),启动完成。
也就是
调用 SpringApplication.run() 进入启动流程。
准备环境,加载配置文件。
创建并初始化 Spring 容器。
执行 refresh() 扫描、加载、初始化所有 Bean。
启动 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 区别
- 来源不同
@Autowired 是 Spring 提供的注解,切换 IOC 容器需要改代码。
@Resource 是 JDK 标准注解,容器迁移不用改代码。
- 查找顺序不同
@Autowired :先按类型(byType)查找,类型找到多个,再按字段名匹配。
@Resource :先按名称(byName)查找,名称找不到,再按类型查找。
- 使用位置不同
@Autowired 可以用在:字段、set 方法、构造器。
@Resource 只能用在:字段、set 方法,不能用在构造器。
- 多 Bean 冲突处理
@Autowired 按类型找到多个 Bean,会自动按字段名匹配,匹配失败报错,可用 @Qualifier 指定 Bean 名称。
@Resource 先按名称匹配,名称找不到再按类型,类型找到多个也会报错,可直接用 name 属性或 @Qualifier 指定。
- 是否允许依赖不存在
@Autowired(required = false) :允许依赖不存在,值为 null。
@Resource 没有 required 属性,找不到 Bean 直接抛异常。
Spring 为什么不推荐使用字段注入,更推荐构造器注入
Spring 不推荐使用字段注入,更推荐构造器注入,原因主要有这几点:
- 更容易发现是否违反单一职责原则
字段注入时,依赖加得再多,代码看起来都很清爽,不容易察觉类太臃肿、职责太多。
换成构造器注入,参数一多就会显得很臃肿,能倒逼我们去拆分类,更符合设计原则。
- 单元测试更方便,不强依赖 Spring 容器
字段注入必须启动 Spring 容器才能赋值;
构造器注入可以直接 new 对象,手动传入 Mock 依赖,不用启动容器就能做单元测试。
- 可以用 final,保证不可变、更安全
字段注入不能用 final 修饰;
构造器注入可以声明为 private final ,依赖不可变、线程更安全。
- 避免初始化顺序导致的空指针 NPE
对象初始化顺序:
类加载 → 静态代码块 → 构造方法执行 → 才进行 @Autowired 字段注入。
如果在构造方法里使用字段注入的对象,此时依赖还没注入,直接空指针。
构造器注入在创建对象时就把依赖传进来,从根源避免 NPE。
- 循环依赖能更早暴露
字段注入的循环依赖,Spring 靠三级缓存可以勉强运行,运行时才可能出问题;
构造器注入的循环依赖,项目启动时直接报错,更早发现、更早解决。
- 封装性更好,不破坏封装
字段注入相当于直接操作类内部字段,破坏封装性;
构造器注入只通过构造方法传入依赖,类的内部状态对外不可见,更符合面向对象封装。