本文首发于 blog.cc1234.ccjava
本文最耗时间的点就在于想一个好的标题, 既要灿烂夺目,又要光华内敛,事实证实这比砍需求还要难!算法
因为对象之间的依赖关系常常是错综复杂,使用不当会引起不少意想不到的问题, 一个很典型的问题就是循环依赖 (也能够称之为循环引用)。缓存
Spring 为咱们提供了依赖注入,而且在某些情景(单例 Bean 的注入)下支持循环依赖的注入markdown
本文的主要目的是分析 Spring 在 Bean 的建立中是如何处理循环依赖的。app
我会从循环依赖是什么,以及它的坏处,到最后经过Spring的源码来看它是如何处理这个问题的。ide
循环依赖不只仅是 Spring 的 Bean 之间会产生, 往大了看,系统模块之间会产生循环依赖, 系统与系统之间也会产生循环依赖,这是一个典型的坏味道,咱们应该尽可能避免。oop
循环依赖指的是多个对象之间的依赖关系造成一个闭环。测试
下图展现了两个对象 A 和 B 造成的一个循环依赖ui
下图展现了多个对象造成的一个循环依赖this
现实中因为依赖层次深、关系复杂等因素, 致使循环依赖可能并非那么一目了然。
循环依赖会为系统带来不少意想不到的问题,下面咱们来简单讨论一下
1、循环依赖会产生多米诺骨牌效应
换句话说就是牵一发而动全身,想象一下平静的湖面落入一颗石子,涟漪会瞬间向周围扩散。
循环依赖造成了一个环状依赖关系, 这个环中的某一点产生不稳定变化,都会致使整个环产生不稳定变化
实际的体验就是
2、循环依赖会致使内存溢出
参考下面的代码
public class AService {
private BService bService = new BService();
}
public class BService {
private AService aService = new AService();
}
复制代码
当你经过 new AService()
建立一个对象时你会得到一个栈溢出的错误。
若是你了解 Java 的初始化顺序就应该知道为何会出现这样的问题。
由于调用 new AService()
时会先去执行属性 bService 的初始化, 而 bService 的初始化又会去执行 AService 的初始化, 这样就造成了一个循环调用,最终致使调用栈内存溢出。
下面咱们经过简单的示例来展现 Spring 中的循环依赖注入, 我分别展现了一个构造器注入和 Field 注入的循环依赖示例
构造器注入
@Service
public class AService {
private final BService bService;
@Autowired
public AService(BService bService) {
this.BService = bService
}
}
复制代码
@Service
public class BService {
private final AService aService;
@Autowired
public BService(AService aService) {
this.aService = aService;
}
}
复制代码
Field注入
@Service
public class AService {
@Autowired
private BService bService;
}
复制代码
@Service
public class BService {
@Autowired
private AService aService;
}
复制代码
Setter
注入和 Feild注入 相似
若是你启动 Spring 容器的话, 构造器注入的方式会抛出异常 BeanCreationException , 提示你出现了循环依赖。
可是 Field 注入的方式就会正常启动,并注入成功。
这说明 Spring 虽然可以处理循环依赖,但前提条件时你得按照它可以处理的方式去作才行。
好比 prototype 的 Bean 也不能处理循环依赖的注入,这点咱们须要注意。
在咱们具体分析 Spring 的 Field 注入是如何解决循环依赖时, 咱们来看看如何到检测循环依赖
在一个循环依赖的场景中,咱们能够肯定如下约束
明确后,咱们就能知道检测循环依赖本质就是在检测一个图中是否出现了环, 这是一个很简单的算法问题。
利用一个 HashSet
依次记录这个依赖关系方向中出现的元素, 当出现重复元素时就说明产生了环
, 并且这个重复元素就是环的起点。
参考下图, 红色的节点就表明是循环出现的点
以第一个图为例,依赖方向为 A->B->C->A ,很容易检测到 A 就是环状点。
Spring 可以处理 单例Bean 的循环依赖(Field注入方式),本节咱们就经过纸上谈兵的方式来看看它是如何作到的
首先,咱们将 Spring 建立 Bean 的生命周期简化为两个步骤:实例化 -> 依赖注入, 以下图所示
实例化就至关于经过 new
建立了一个具体的对象, 而依赖注入就至关于为对象的属性进行赋值操做
咱们再将这个过程扩展到两个相互依赖 Bean 的建立过程上去, 以下图所示
A 在执行依赖注入时须要实例化 B, 而 B 在执行依赖注入时又会实例化 A ,造成了一个很典型的依赖环。
产生环的节点就是 B 在执行依赖注入的阶段, 若是咱们将其"砍”掉, 就没有环了, 以下图所示
这样作确实没有循环依赖了,但却带来了另外一个问题,B 是没有通过依赖注入的, 也就是说 B 是不完整的, 这怎么办呢?
此时 A 已经建立完成并维护在 Spring 容器内,A 持有 B 的引用, 而且 Spring 维护着未进行依赖注入的 B 的引用
当 Spring 主动建立 B 时能够直接取得 B 的引用 (省去了实例化的过程), 当执行依赖注入时, 也能够直接从容器内取得 A 的引用, 这样 B 就建立完成了
A 持有的未进行依赖注入的 B,和后面单首创建 B 流程里面是同一个引用对象, 当 B 执行完依赖注入后,A 持有的 B 也就是一个完整的 Bean了。
没有代码的泛泛而谈是没有灵魂的
我画了一个简化的流程图来展现一个 Bean 的建立(省略了 Spring 的 BeanPostProcessor,Aware 等事件)过程, 但愿你过一遍,而后咱们再去看源码。
入口直接从 getBean(String)
方法开始, 以 populateBean
结束, 用于分析循环依赖的处理是足够的了
getBean(String)
是 AbstractBeanFactory 的方法, 它内部调用了 doGetBean
方法, 下面是源码:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly){
...
// #1
Object sharedInstance = getSingleton(beanName);
...
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (mbd.isSingleton()) {
// #2
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// #3
return createBean(beanName, mbd, args);
}
});
}
...
return (T)bean;
}
}
复制代码
我简化了 doGetBean
的方法体,与流程图对应起来,使得咱们能够轻松找到下面的调用流程
doGetBean -> getSingleton(String) -> getSingleton(String, ObjectFactory)
复制代码
getSingleton
是 DefaultSingletonBeanRegistry 的重载方法
DefaultSingletonBeanRegistry 维护了三个 Map 用于缓存不一样状态的 Bean, 稍后咱们分析 getSingleton
时会用到
/** 维护着全部建立完成的Bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** 维护着建立中Bean的ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** 维护着全部半成品的Bean */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
复制代码
getSingleton(String)
调用了重载方法 getSingleton(String, boolean)
, 而该方法实际就是一个查询 Bean 的实现, 先看图再看代码:
从图中咱们能够看见以下查询层次
singletonObjects => earlySingletonObjects => singletonFactories
复制代码
再结合源码
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
@Override
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从singletonObjects获取已建立的Bean
Object singletonObject = this.singletonObjects.get(beanName);
// 若是没有已建立的Bean, 可是该Bean正在建立中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 从earlySingletonObjects获取已经实例化的Bean
singletonObject = this.earlySingletonObjects.get(beanName);
// 若是没有实例化的Bean, 可是参数allowEarlyReference为true
if (singletonObject == null && allowEarlyReference) {
// 从singletonFactories获取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 使用ObjectFactory获取Bean实例
singletonObject = singletonFactory.getObject();
// 保存实例, 并清理ObjectFactory
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
复制代码
经过 getSingleton(String)
没有找到Bean的话就会继续往下调用 getSingleton(String, ObjectFactory)
, 这也是个重载方法, 源码以下
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
// 获取缓存的Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
...
// 标记Bean在建立中
beforeSingletonCreation(beanName);
boolean newSingleton = false;
...
// 建立新的Bean, 实际就是调用createBean方法
singletonObject = singletonFactory.getObject();
newSingleton = true;
...
if (newSingleton) {
// 缓存bean
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
复制代码
流程很清晰,就不必再画图了,简单来讲就是根据 beanName 找不到 Bean 的话就使用传入的 ObjectFactory 建立一个 Bean。
从最开始的代码片断咱们能够知道这个 ObjectFactory 的 getObject 方法实际就是调用了 createBean
方法
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// #3
return createBean(beanName, mbd, args);
}
});
复制代码
createBean
是 AbstractAutowireCapableBeanFactory 实现的,内部调用了 doCreateBean
方法
doCreateBean
承担了 bean 的实例化,依赖注入等职责。
参考下图
createBeanInstance
负责实例化一个 Bean 对象。
addSingletonFactory
会将单例对象的引用经过 ObjectFactory 保存下来, 而后将该 ObjectFactory 缓存在 Map 中(该方法在依赖注入以前执行)。
populateBean
主要是执行依赖注入。
下面是源码, 基本与上面的流程图保持一致, 细节的地方我也标了注释了
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
...
return doCreateBean(beanName, mbdToUse, args);
}
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
...
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
// 容许单例Bean的提早暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 新建并缓存ObjectFactory
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// 若是忽略BeanPostProcessor逻辑, 该方法实际就是直接返回bean对象
// 而这里的bean对象就是前面实例化的对象
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
...
// 依赖注入
populateBean(beanName, mbd, instanceWrapper);
...
}
}
复制代码
若是你仔细看了上面的代码片断,相信你已经找到 Spring 处理循环依赖的关键点了
咱们以 A,B 循环依赖注入为例,画了一个完整的注入流程图
注意上图的黄色节点, 咱们再来过一下这个流程
addSingleFactory
(黄色节点)方法缓存, 而后执行依赖注入B。getSingleton
方法获得这个 A 的提早引用(拿到最开始缓存的 objectFactory, 经过它取得对象引用), 这样 B 的依赖注入就完成了。这样整个依赖注入的流程就完成了
又到了总结的时候了,虽然全文铺的有点长,可是 Spring 处理单例 Bean 的循环依赖却并不复杂,并且稍微扩展一下,咱们还能够将这样的处理思路借鉴一下从而处理相似的问题。
不可避免的文章仍是留下了很多坑,好比
固然这些都能在 Spring 建立 Bean 的流程里面找到(getBean(String) 方法),细节的东西就留给读者本身去源码里面发现了哦