循环依赖其实就是循环引用, 简单来说, 就是两个或两个以上的bean互相持有对象, 形成闭环. 例如A依赖于B, B依赖于C, 而C又依赖于A
Spring单例对象的初始化主要分为三个步骤:
循环依赖主要发生在第一、第二步骤, 也就是构造器循环依赖和属性循环依赖
Spring解决循环依赖也是从bean的初始化过程着手的, 对于单例来说, 在Spring容器整个生命周期内, 有且只有一个对象, 所以很容易想到这个对象应该存在Cache中, Spring为了解决单例的循环依赖, 使用了三级缓存
所谓三级缓存, 其实就是存放不同状态下的bean
下面的代码就是我们获取bean的逻辑
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
上面的代码需要解释两个参数:
Spring解决循环依赖的诀窍是依赖于singletonFactories这个三级缓存
三级缓存的类型是ObjectFactory, 它是一个泛型接口, 只有一个方法
AbstractAutowireCapableBeanFactory#doCreateBean 我们一起来看下源码
如果bean是单例, 同时允许从singletonFactories获取bean,并且当前bean正在创建中, ,那么就把beanName放入三级缓存(singletonFactories)中:
这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
Spring解决循环依赖是通过三级缓存, 核心思想是将bean的实例化和属性赋值这两个过程剥离
我们先来简单了解一下三级缓存:
一级缓存: 保存实例化、注入、初始化完成的bean实例
二级缓存: 保存实例化完成的bean实例
三级缓存: 用于保存bean创建工厂,以便于后面扩展有机会创建代理对象
二级缓存的作用
三级缓存为什么可以提前暴露?
AOP除了后置处理(默认) , 还有一个提前处理, 简单来说AOP有两个入口, 通过提前处理就可以在实例化后, 属性赋值之前, 完成对bean的AOP, 将最终的bean存储到二级缓存中
为什么不能只使用二级缓存?
二级缓存是为了存储bean的实例化对象,它无法感知bean是否需要进行aop(实例化>属性赋值>后置处理)
参考内容:
spring如何解决循环依赖
spring为什么要使用三级缓存解决循环依赖
spring三级缓存之为大多数合理而设计
IOC容器的启动过程分为两个步骤
Spring中, 最基础的容器接口方法都是由BeanFactory定义的, 而BeanFactory的实现类采用了延迟加载, 而ApplicationContext在启动容器时就完成了所有bean的初始化
这个阶段主要是根据程序中定义的xml或者注解等bean的声明方式, 通过解析和加载后, 生成BeanDefinition, 然后将BeanDefinition注册到IOC容器中去
通过注解或者xml声明的bean都会解析得到一个BeanDefinition实体, 这个实体里会包含Bean的一些定义和基本的一些属性, 最终将BeanDefinition保存到一个map集合中, 从而去完成IOC的一个初始化, IOC容器的作用就是对这个bean的注册信息进行处理和维护
这个阶段会做两件事, 一是通过反射去实例化bean, 完成bean的依赖注入, 二是bean的使用, 通常我们通过@Autowired从IOC容器中获取指定bean的实例
SpringBoot的启动类上有一个@SpringBootApplication注解, 这个注解是SpringBoot项目必不可少的注解.自动配置的原理也跟这个注解有千丝万缕的关系
@SpringBootApplication是一个复合注解, 在其中有一个注解@EnableAutoConfiguration, 顾名思义: 开启自动配置
这个注解也是一个派生注解, 其中的关键功能是下面两个
一、@AutoConfigurationPackage
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
/**
* 根据传入的元注解信息获取所在的包, 将包中组件类封装为数组进行注册,
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
AutoConfigurationPackages将主配置类( @SpringBootApplication )所在的包及其子包里面的所有组件扫描到IOC容器中
二、@Import({AutoConfigurationImportSelector.class})
我们先来了解下SpringBoot加载配置类的方式
为什么不用@ComponentScan?
这里不会使用@ComponentScan, 因为使用它很不方便, 开发人员需要记住所有三方jar包中的package名称, 写入到程序中
为什么不导入普通类?
这比使用@ComponetScan还差劲, 因为需要记住第三方Jar包中的具体类名
为什么不导入注册器?
也不恰当, BeanDefinition注册器的设计目标是对@Bean方法的一个补充, 从名字就可以看出, 它针对的是BeanDefinition层面的
AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。
spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。
参考内容:
Spring Boot面试杀手锏————自动配置原理
SpringBoot自动配置原理
[spring三级缓存之为大多数合理而设计