Spring Bean 「1」依赖

247 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

01-依赖注入

Spring IoC 中支持两种 DI 方式:

  • 构造器 DI,Spring 无法解决构造器 DI 时的循环依赖。
  • Setter-based DI

01.1-延迟(懒)实例化

默认情况下,容器(BeanFactory / ApplicationContext)会在初始化阶段创建单例 Bean,这个过程称之为预实例化(pre-instantiation)。Spring 提供了配置方式来延迟实例化某些单例 Bean,称之为 lazy-initialized。

  • XML 中,使用lazy-init
  • Java 中,使用@Lazy

01.2-Method Injection

应用场景:当单例 Bean 依赖原型 Bean 时,由于单例 Bean 的依赖注入只有一次机会,即创建该单例 Bean 时,所以单例 Bean 中使用的永远是其创建时被注入的同一个对象。这违背了原型 Bean 的设计原则。

解决上述问题的一种方式是,使用容器的getBean方法,每次需要是从容器中获得一个新的原型 Bean。但这并不是一个优雅的解决方案,因为 Spring 框架侵入到了应用层代码中,使得 Spring 与应用层代码耦合在一起。

doc.spring.io 中的CommandManager为例:

public class CommandManager implements ApplicationContextAware {
		private ApplicationContext applicationContext;
		// ... other codes
    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }
		// ... other codes
}

Spring 提供了另一种解决方案,方法注入(method injection),其实现基于 GCLIB 动态代理。

public abstract class CommandManager {
		// ... other codes
    // okay... but where is the implementation of this method?
		@Lookup("myCommand")
    protected abstract Command createCommand();
}

Spring 会借助 CGLIB 为CommandManager生成一个动态代理类,该类实现了@Lookup注解标注的方法,该方法将会去容器中查找名称为”myCommand”的原型 Bean 并返回(lookup by name)。

或者,也可以写成如下方式:

public abstract class CommandManager {
		// ... other codes
    // okay... but where is the implementation of this method?
		@Lookup
    protected abstract MyCommand createCommand();
}

MyCommandCommand的子类(lookup by type)。

02-循环依赖

从前面章节的介绍中了解到,Spring 在创建类 A 的实例时,如果发现 A 依赖于类 B,则会先去创建 B,然后再将 B 的实例装配到 A 的实例中。

循环依赖描述的是一下场景:类 A 依赖于类 B,类 B 又依赖于类 C,而类 C 反过来又依赖 A。

Spring 中创建类的实例 Bean 时,步骤如下:

  1. createBeanInstance,实例化
  2. populateBean,属性注入
  3. initializeBean,初始化

配合上述三个步骤,Spring 设计了三层缓存来解决循环依赖:

  1. singletonObjects,一级缓存,存储着所有 ready-to-use 的单例 Bean
  2. earlySingletonObjects,二级缓存,完成实例化(1.)但未完成(2-3)的单例 Bean
  3. singletonFactories,三级缓存,提前暴露的单例工厂,2中存储的为从单例工厂中获得的 Bean
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,2]。

假设类 A 依赖类 B,同时类 B 依赖类 A。

  1. Spring 创建类 A 的实例时,执行完第1.步实例化后,在第2.步发现依赖类 B,尝试 getSingleton 来获取 B 的单例 Bean。注:在创建类 A 的实例 Bean 时,会先先创建一个与 Bean 同名的单例工厂。即在三级缓存中存入 beanName → 单例工厂。
  2. 创建类 B 的实例时,执行完第1.步实例化后,在第2.步发现依赖类 A,尝试 getSingleton 来获取 A 的单例 Bean。在一、二级缓存中没有找到,但在三级缓存中找到。于是,类 B 的单例 Bean 完成了第2.步属性注入,然后完成第3.步初始化。
  3. B 的单例 Bean ready-to-use 后,会继续创建 A 实例的第2-3步。

注:如果使用构造器 DI 方式时,发生了循环依赖,上述步骤时无法解决的,因为创建类 A 的单例 Bean 的第1.步就无法完成。此时,可以将构造器 DI,修改为 setter-based DI。

  1. 面试必杀技,讲一讲Spring中的循环依赖
  2. Spring-bean的循环依赖以及解决方式
  3. Spring是如何解决循环依赖的