灵魂拷问环节
Q1: Spring 中,什么时候会出现循环依赖,导致无法正常启动?
循环依赖问题的出现,其核心就是在循环依赖的多个类中,实例化类的同时,也在设置循环依赖的对象。
在 Spring 中,依赖注入有三种方式:
- 基于构造方法的依赖注入
- 基于 setter() 方法的依赖注入
- 基于 成员变量 的依赖注入
发生循环依赖时,Spring 框架可以 自动帮我们解决,Bean Scope 为 singleton,且是基于成员变量或 setter() 方法的方式注入依赖。其原因就是 singleton 类型的 Bean 在实例化后,被添加到了 Bean 集合的缓存中,具体的源码逻辑下文有介绍。
基于构造器 DI 的循环依赖,Spring中没有自动解决,但可以通过在构造器上添加 @Lazy 注解,产生一个 CGlib代理类来手动解决这个问题。
其他 Scope Bean 产生的循环依赖,无法解决。
Q2: Spring 是如何解决 singleton 类型 Bean 循环依赖的?
- Spring 通过 提前曝光 的方式,将未完成 “初始化”的 BeanA,设值为 ObjectFactory,并存入单例类工厂缓存 singletonFactories 中(addSingletonFactory),然后再通过 getSingleton 将Bean 填充 earlySingletonObjects 缓存中 (该步骤都在AbstractAutowireCapableBeanFactory#doCreateBean方法中)
- 循环依赖的 BeanB 初始化会查找 BeanA ,便可以从 earlySingletonObjects 缓存中找到 earlySingletonRefrence,此时就已经打破了循环依赖的问题。
其中两步对应的关键代码如下:
DefaultSingletonBeanRegistry:
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
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;
}
AbstractAutowireCapableBeanFactory:
protected Object doCreateBean(
final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
.........
// Eagerly cache singletons to be able to resolve circular references even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure =
(mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName
+"' to allow for resolving potential circular references");
}
//将未完成 “初始化”的 BeanA,设值为 ObjectFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}catch(...){...}
if (earlySingletonExposure) {
//将Bean填充到earlySingletonObjects 缓存中
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
.........
}
}
return exposedObject;
}
Q3: 为什么要三级缓存?二级缓存不能解决循环依赖吗?
二级缓存配合也可以解决循环依赖,但三级缓存的使用,是为了完成 Spring AOP 中的后置处理 功能而提出的。
Q4:如果 非要用 二级缓存来解决上面提到的两个问题,如何来实现?为什么 Spring 没有采用你说的这种方案?
二级缓存架构下,为了获取到 AOP 代理后的对象,需要对 Bean A 在 实例化 后,完成代理,然后放入二级缓存提前曝光,保证 Bean B 在填充类属性时,可以从二级缓存获取到代理后的 Aop-A,以实现 Spring Aop 的代理功能以及解决 A<=>B 的循环依赖问题。
上述的逻辑并没有被 Spring 采用,是因为上述流程和 Spring Bean 生命周期的设计相互冲突了。 Bean 生命周期的最后一步才是完成 AOP 代理,而不是二级缓存架构中提到的在实例化后,就完成代理。
Q5:AOP 代理对象提前放入了三级缓存,没有经过初始化和属性填充,这个代理又是如何保证依赖属性的注入的呢?
AOP 中的代理对象,保存着 traget 也就是是 原始bean 的引用,因此后续 原始bean 的完善,也就相当于 Spring AOP 中的 target 的完善,这样就保证了 AOP 的 属性填充 与 初始化 了!
Q6: 明明初始化的时候是A对象,那么 Spring 是在哪里将 A的代理对象放入到容器中 的呢?
在完成 Bean A 的初始化 后,Spring 又调用了一次 getSingleton 去缓存中获取对象,但这次是禁用三级缓存的获取方式(allowEarlyReference = false)。在这之前,为 Bean B 中注入 Bean A 时,已经将 A 从三级缓存中的工厂取出,并从工厂中获取到了Aop-A(半成品)放入到了二级缓存中。所以初始化完成后的 getSingleton 调用,拿到的是二级缓存中 Aop-A 对象,最后 将 Aop-A 移除二级缓存,放入一级缓存中。然后继续执行 Bena 接下来的流程。
Q7: 回到 Q1 ,提到的两种方式中,为什么没有解决循环依赖问题?
- 对于使用了构造器的循环依赖,由于 无法通过构造器生成 向缓存中存入的 实例化后的 Bean ,所以 Spring 会抛出异常。
- 对于 prototype 作用域的 Bean ,Spring 容器没有设计缓存机制,因此也无法提前暴露一个创建中的 Bean。
原因也比较简单,prototype 作用域的 Bean 每次请求都会创建一个实例对象,如果使用缓存,在请求量比较大的场景下,对 GC 的压力会非常大,所以 Spring 直接抛出异常。
OK,如果你对于上面6个问题都能比较轻松的回答到的话,那么恭喜你,你的 Spring 学习已经可以毕业了,可以和面试官互相吹逼了。如果小老弟还一问三不知,三问傻痴痴的,那么接着往下学习吧(对应的源码太多了,阅读起来费时费力,后面尽量少贴)。
Bean 创建、获取
首先,需要知道,Spring 在创建 Bean 的时候默认是按照自然排序来进行创建的,其过程主要分为4 个阶段(Bean的生命周期):
- 实例化(Instantiation),简单理解就是使用空参构造器 new 了一个对象
- 属性注入(Populate),为实例化中 new 出来的对象填充属性
- 初始化(Initialization),进行初始化,并实现 Spring AOP 代理
- 销毁(Destruction),回调在使用前注册的销毁方法
然后我们来看一张不包含 Spring AOP 的,循环依赖 Bean 创建流程图:
图中涉及到了三级缓存,他们的区别如下:
-
singletonObjects(单例对象缓存):用于存储 已经创建完成且属性已经注入完成 的Bean实例。
-
earlySingletonObjects(早期单例对象缓存):用于存储 已经创建完成但属性还未注入完成 的Bean实例,解决循环依赖。
-
singletonFactories(单例类工厂缓存):用于存储Bean实例的工厂对象,即创建Bean实例的工厂方法。二级缓存中存储的就是从这个工厂中获取到的对象,解决代理 Bean 的问题。
DefaultSingletonBeanRegistry:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
包含 Spring AOP 的,循环依赖 Bean 创建流程图(两张):
拿着这3张图,然后再去对比源码阅读,下面是一些重要的代码片段,多的就不贴了,影响阅读体验。
AbstractBeanFactory:
/**
* Return an instance, which may be shared or independent, of the specified bean.
* @param name the name of the bean to retrieve
* @param requiredType the required type of the bean to retrieve
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @return an instance of the bean
* @throws BeansException if the bean could not be created
*/
public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
throws BeansException {
return doGetBean(name, requiredType, args, false);
}
/**
* Return an instance, which may be shared or independent, of the specified bean.
* @param name the name of the bean to retrieve
* @param requiredType the required type of the bean to retrieve
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @param typeCheckOnly whether the instance is obtained for a type check,
* not for actual use
* @return an instance of the bean
* @throws BeansException if the bean could not be created
*/
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);//从三个缓存中获取bean
……
//当无法从三处缓存中获取到bean时执行
// 开始创建bean。如下片段为创建单例bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
DefaultSingletonBeanRegistry:
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not.本次调用链路下,将传入true
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
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) {
/**
* 三级缓存获取,key=beanName value=objectFactory,objectFactory中存储getObject()方法用于获取提前曝光的实例
*
* 而为什么不直接将实例缓存到二级缓存,而要多此一举将实例先封装到objectFactory中?
* 主要关键点在getObject()方法并非直接返回实例,而是对实例又使用
* SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法对bean进行处理
*
* 也就是说,当spring中存在该后置处理器,所有的单例bean在实例化后都会被进行提前曝光到三级缓存中,
* 但是并不是所有的bean都存在循环依赖,也就是三级缓存到二级缓存的步骤不一定都会被执行,有可能曝光后直接创建完成,没被提前引用过,
* 就直接被加入到一级缓存中。因此可以确保只有提前曝光且被引用的bean才会进行该后置处理
*/
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
/**
* 通过getObject()方法获取bean,通过此方法获取到的实例不单单是提前曝光出来的实例,
* 它还经过了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法处理过。
* 这也正是三级缓存存在的意义,可以通过重写该后置处理器对提前曝光的实例,在被提前引用时进行一些操作
*/
singletonObject = singletonFactory.getObject();
// 将三级缓存生产的bean放入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 删除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
Bean的生命周期
其过程主要分为4 个阶段:
-
实例化,简单理解就是使用空参构造器 new 了一个对象
-
属性注入,为实例化中 new 出来的对象填充属性
-
初始化,第 3、4 步为在初始化前执行,第 5、6 步为初始化操作,第 7 步在初始化后执行,该阶段结束,才能被用户使用。第 3、4、7完成了 Spring AOP 功能
-
销毁(Destruction)
AbstractAutowireCapableBeanFactory:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 1. 实例化
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object exposedObject = bean;
try {
// 2. 属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 3. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
// 4. 销毁-注册回调接口
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
return exposedObject;
}
生命周期中常用的前、后置API
分类:
- Constructor:类构造方法
- @PostConstruct:javax.annotation.PostConstruct
- afterPropertiesSet:InitializingBean#afterPropertiesSet
- init-method:
实际的执行顺序可查看日志编号:
@Configuration
@Component
public class BeanLifecycleTest implements InitializingBean, DisposableBean {
public final Logger logger = LoggerFactory.getLogger(this.getClass());
public BeanLifecycleTest() {
logger.info("1 BeanLifecycleTest Construct");
}
@PostConstruct
public void postConstruct() {
logger.info("2 BeanLifecycleTest @PostConstruct ");
}
@Override
public void afterPropertiesSet() throws Exception {
logger.info("3 BeanLifecycleTest InitializingBean#afterPropertiesSet ");
}
@Bean(initMethod = "init")
public void initMethod() {
logger.info("4 BeanLifecycleTest @Bean.initMethod");
}
@Bean(destroyMethod = "destory")
public void destroyMethod() {
//标注destroyMethod后,会在bean初始化完成后,调用该销毁方法
logger.warn("5 BeanLifecycleTest @Bean.destroyMethod");
}
@PreDestroy
public void preDestroy() {
//todo 为什么6在7之前????
logger.warn("6 BeanLifecycleTest @PreDestroy");
}
@Override
public void destroy() throws Exception {
logger.warn("7 BeanLifecycleTest DisposableBean#destroy");
}
}
nitmethod
---->