Spring 是面试中的重要部分,其中有几个关键问题在面试中出现的频率特别高,比如 Spring Bean 的生成流程,SpringBoot 的启动流程,Spring 循环依赖,笔者对于这些知识点总是记了忘,忘了记,所以对这些知识点做一个汇总。
Spring 生成 Bean 流程
以单例对象为例子,说一个 Bean 的极简流程以及其目的
- 获取 Bean定义:扫描工程内所有被标记的 Bean,获取其类型,名称,属性,构造方法等信息,存在一个 Map里
- 生成实例:这一步也很简单,遍历上述 Map,利用 Bean 定义里的无参构造方法创建对象,和 new 对象同理
- 属性装填:刚创建的对象所有属性都是默认值,需要我们给它装填上需要的内容
- 初始化:如果这个 Bean 实现了 InitializingBean 接口,则会调用你写在 afterPropertiesSet 方法里的内容。
上面四步是核心功能,Spring 为了增强对这些 Bean 的修改能力,在 2,3,4 步骤的前后都预留了处理点,Spring 自己或用户都可以通过编写 Bean 后置处理器(BeanPostProcessor)来实现自己的目的,这些处理器会在对应的处理点被执行,从而完成对Bean的修改。Spring 中的后置处理器分为两大类:BeanFactoryPostProcessor 和 BeanPostProcessor,BeanFactoryPostProcessor 针对 Bean工厂,调整 Bean 工厂的属性、影响 Bean 定义,注意此时还没有 Bean 进行实例化。 BeanPostProcessor 则更直接的作用于 Bean实例生成过程中的修改 。
如图:

Spring Boot 启动流程
为什么 SpringApplication.run 可以启动一个 Web 应用?步骤如下:
- 初始化 SpringApplication 信息。
- 加载配置文件(application.properties)、环境变量。
- 创建 Spring 上下文。
- 预初始化上下文,读取配置类,BeanDefinition。
- 刷新容器,加载 IOC 容器。
- 加载自动配置类,解析@Import。
- 启动 Web 服务器,onRefresh()。
将启动类作为配置类进行读取,其中会启动 Spring 事件监听,也会发布很多事件(9次)。
详细如图:

Spring Boot 自动配置
Spring Boot 自动配置是 Spring Boot 框架的一项核心特性,它可以基于应用程序的依赖关系和配置信息,自动配置应用程序所需的 Spring Bean。Spring Boot 自动配置是通过条件化配置实现的,这意味着只有在特定条件下才会应用这些配置。这些条件可以是应用程序的依赖关系、配置值、环境变量等等。
loadFactoryNames() 方法的作用是:加载所有使用了 @EnableAutoConfiguration 注解的自动配置类的全限定类名,并返回一个 List 类型的对象,其中包含了所有的候选自动配置类的全限定类名。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
debug 如图:

loadSpringFactories 方法是去 "META-INF/spring.factories" 真正的获取自动配置。
SpringBoot 自动配置就是基于SpringFactories 机制获取对应依赖META-INF目录下的 spring.factories 文件中的需要自动配置的类的全限定名信息,然后根据这些信息将我们需要的使用的 Bean 对象放到 IOC 容器中,当我们需要使用时,通过依赖注入机制直接注入使用即可。当然如果再追问具体的实现细节,可以根据我们的分析流程讲讲具体的代码实现。
Spring Boot 循环依赖
循环依赖:多个对象之间存在循环的引用关系,在初始化过程当中,就会出现"先有蛋还是先有鸡"的问题。
1、一种是使用@Lazy注解:解决构造方法造成的循环依赖问题。
2、另一种是使用三级缓存
-
一级缓存:缓存最终的单例池对象:
private final Map<String,Object>singletonObjects=neWConcurrentHashMap<>(256);
-
二级缓存:缓存初始化的对象,对于对象之间的普通引用,二级缓存会保存 new 出来的不完整对象,这样当单例池中找到不依赖的属性时,就可以先从二级缓存中获取到不完整对象,完成对象创建,在后续的依赖注入过程中,将单例池中对象的引用关系调整完成。
private final Map<String,Object>:earlySingletonObjects=newConcurrentHashMap<>(16);
-
三级缓存:缓存对象的ObjectFactory,如果引用的对象配置了AOP,那在单例池中最终就会需要注入动态代理对象,而不是原对象。而生成动态代理是要在对象初始化完成之后才开始的。于是Spig增加三级缓存,保存所有对象的动态代理配置信息。在发现有循环依赖时,将这个对象的动态代理信息获取出来,提前进行AOP,生成动态代理。
private final Map<String,ObjectFactory<?>>singletonFactories=newHashMap<>(16)
核心代码就在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry 的getSingleton,方法当中。
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
// 二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
// 双重检测锁
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 三级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
三级缓存不单单是解决循环依赖,但是在循环依赖中很有作用。
参考文章
- SpringBean 生成流程详解:https://blog.csdn.net/u011709538/article/details/129303025
- 了解 SpringBoot 启动过程:https://cloud.tencent.com/developer/article/2131866
- SpringBoot 自动配置:https://juejin.cn/post/7046554366068654094
- SpringBoot 自动配置原理详解:https://juejin.cn/post/7228780174312751161