Spring 是面试中的重要部分,其中有几个关键问题在面试中出现的频率特别高,比如 Spring Bean 的生成流程,SpringBoot 的启动流程,Spring 循环依赖,笔者对于这些知识点总是记了忘,忘了记,所以对这些知识点做一个汇总。

Spring 生成 Bean 流程

以单例对象为例子,说一个 Bean 的极简流程以及其目的

  1. 获取 Bean定义:扫描工程内所有被标记的 Bean,获取其类型,名称,属性,构造方法等信息,存在一个 Map里
  2. 生成实例:这一步也很简单,遍历上述 Map,利用 Bean 定义里的无参构造方法创建对象,和 new 对象同理
  3. 属性装填:刚创建的对象所有属性都是默认值,需要我们给它装填上需要的内容
  4. 初始化:如果这个 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 应用?步骤如下:

  1. 初始化 SpringApplication 信息。
  2. 加载配置文件(application.properties)、环境变量。
  3. 创建 Spring 上下文。
  4. 预初始化上下文,读取配置类,BeanDefinition。
  5. 刷新容器,加载 IOC 容器。
  6. 加载自动配置类,解析@Import。
  7. 启动 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;
}

三级缓存不单单是解决循环依赖,但是在循环依赖中很有作用。

参考文章

  1. SpringBean 生成流程详解:https://blog.csdn.net/u011709538/article/details/129303025
  2. 了解 SpringBoot 启动过程:https://cloud.tencent.com/developer/article/2131866
  3. SpringBoot 自动配置:https://juejin.cn/post/7046554366068654094
  4. SpringBoot 自动配置原理详解:https://juejin.cn/post/7228780174312751161