在应用程序中,经常会有循环依赖的场景,比如A引用了B,B引用了A,spring对于这种情况的处理是通过三级缓存来实现的,其实就是内部维护了三个map
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
下面以这样的实例来跟踪下spring的处理流程,来看下spring是如何利用三级缓存来实现循环依赖的,这个例子中Student引用了Teacher,而Teacher引用了Student,且还存在一个切面类针对Student的examScore方法
@Component
public class Student {
private String name;
@Autowired
private Teacher teacher;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public int examScore() {
System.out.println("student examScore");
return 1;
}
}
@Component
public class Teacher {
private String name;
@Autowired
private Student student;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
@Aspect
@Component
public class StudentAspect {
@Pointcut("execution(* com.ailra.beans.Student.examScore(..))")
public void studentCut() {
}
@Before("studentCut()")
public void before() {
System.out.println("before");
}
}
应用程序这时要获取一个Student的bean实例时,会执行如下这个方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先看spring的一级缓存是否已经存在了,若存在了,直接返回
Object singletonObject = this.singletonObjects.get(beanName);
//如果不存在,且还在创建的过程中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
刚开始时,在 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))这个判断就会返回false,因为此刻student单例还没被创建,且还未开始
程序继续往后执行,在执行创建对象的工厂方法前后会分别执行这两个方法
//执行创建对象方法前,先将beanName加到singletonsCurrentlyInCreation,表示当前bean实例正在创建中
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
//当bean实例创建完毕后,将beanName从singletonsCurrentlyInCreation移除,表示bean不处于创建过程中
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
所以判断bean是否创建过程,只需要看是否存在singletonsCurrentlyInCreation集合中,也是getSingleton最开始的判断条件之一
接着,一直跟踪到这个方法doCreateBean
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//这里创建好bean的对象了,此时对象的属性值还未赋值
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 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");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
return exposedObject;
}
这个方法是循环依赖最核心的部分,其中省略了部分代码,这里分成以下几个步骤讲解
- 执行到这个方法的Object bean = instanceWrapper.getWrappedInstance();这行代码时,一个普通的student对象创建出来了,但是其成员变量teacher还未赋值
- boolean earlySingletonExposure = ..... 这行代码,早期对象暴露,从后面的条件可以看出,若bean是单例的且允许循环依赖,还有时处于创建过程中的,那么允许将此时的对象暴露出去,但是暴露出去并不是直接将这个对象加到二级缓存的半成品对象map中,而是添加一个对象工厂到三级缓存中,这是因为存在代理对象的情况,也是这个例子中使用切面的原因,这个等会再回过头来看下更清楚些
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
//将工厂方法添加到三级缓存中,并且将其从二级缓存移除
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
//创建对象的工厂方法
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
- populateBean(beanName, mbd, instanceWrapper);执行这行代码时,要对student的属性进行赋值,其中一个属性是teacher引用,所以要先创建一个teacher的实例,所以程序又回到了getSingleton方法
- 然后创建teacher过程中,将beanName加入到singletonsCurrentlyInCreation中,表示teacher正在创建,接着程序执行到doCreateBean方法
- 此时,teacher执行boolean earlySingletonExposure...这行代码时,也是允许暴露早期对象,因此也将创建teacher实例的工厂添加到三级缓存中addSingletonFactory
- 执行populateBean代码时,要对teacher的属性进行赋值,其中就有包含student引用。因此要从spring工厂拿student的单例对象,所以程序此时再次回到getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
但这时,这个方法就会执行到最里层判断的代码了,在前面的步骤2中,已经将student的单例创建工厂添加到三级缓存中,并且将其从二级缓存中移除了,且student单例也处于正在创建的过程中。
所以执行创建student单例的工厂方法getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
这个方法返回的student对象,会和入参的bean对象不一样,因为student有个切面类,所以返回的是一个代理对象,并加入到earlySingletonObjects中,防止后续有其他对象也引用了这个student对象(比如student对象还有一个属性Car对象,Car对象也引用了Student),每次都要执行工厂方法,影响性能,并且工厂方法返回的对象每次都是不一样的。如student对象有存在一级缓存了,也需要移除下。
- 当对teacher的属性都赋值完毕后,程序回到exposedObject = initializeBean(beanName, exposedObject, mbd);这行代码,要对teacher进行初始化操作,执行完初始化操作后,teacher的单例对象就创建完毕了,加入到一级缓存中,后面if (earlySingletonExposure)逻辑后几个步骤解释
- 当teacher的单例对象创建完毕后,会将其从singletonsCurrentlyInCreation移除,并且执行addSingleton方法
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
因为此时teacher的实例实例化且初始化,所以添加到一级缓存中,并将其从二级三级缓存移除掉。
- 当student实例执行完对所有属性都赋值完毕后,程序回到前面的步骤3,然后接着开始执行初始化方法initializeBean
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
//执行实现了aware接口的方法
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
//初始化前执行的方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
//执行初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
}
//初始化后执行的方法
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
注意:这个初始化方法的入参bean是普通的student对象,而不是代理对象,而在执行applyBeanPostProcessorsAfterInitialization这个方法时,由于aspectj有加了一个AnnotationAwareAspectJAutoProxyCreator,这也是一个BeanPostProcessor,所以这里就会执行了,返回一个代理对象
- 执行完initializeBean后,进入了这个代码片段
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
这里又会执行getSingleton,先获取到早期暴露的对象,也就是已经被其他对象引用的实例,然后会执行这个判断if (exposedObject == bean),这是因为exposedObject 有可能在initializeBean方法内被BeanPostProcessor的初始化后的方法给改变了。如果条件判断相等的话,就直接返回早期暴露的对象,如果不相等的话,还会判断这个bean是否被其他bean实例引用了,若有引用,则抛出异常,表示被引用的bean对象与当前最终创建好的bean对象不一致
doCreateBean方法执行完后,再讲bean实例添加到一级缓存中,然后将其从二级和三级缓存移除。
总结一下,spring使用的三级缓存,就是三个map。
- singletonObjects 存储的是最终的对象
- singletonFactories 存储的是创建暴露出去给其他对象引用的早期对象的工厂
- earlySingletonObjects 存储的是暴露出去给其他对象引用的早期对象
那么,为何需要使用3个map?首先一个肯定不行,因为一个要存放的是最终对象,那么中间创建过程中的对象就没地方存储了
如果只使用两个呢?两个的话,我觉得功能上是可以实现的,相当于一个存储最终对象,一个存储的是工厂,每次要获取早期暴露出去的对象,无非是每次都执行一次工厂方法,但是工厂方法内部同样需要使用缓存,不然每次获取的都是不一样的对象了。因此这种方式实际上还是用了三个map来实现,只是第三个放的地方不一样而已
如果两个map,一个存储最终对象,一个存储早期暴露对象,而不是存储工厂呢?这种相当于在addSingletonFactory方法的位置,直接执行工厂方法了,而不是将其缓存,那么执行完这个方法后,马上将早期暴露出去的对象缓存起来。再接着执行后面的populateBean方法,虽然说是可以的,但是相当于说,无论有没存在循环依赖,我都先执行了工厂方法,假如不存在其他对象来引用的话,这个代码执行就多余了,影响性能吧
这里还有需要注意的是,假如不存在循环依赖,那么早期暴露对象对应的工厂方法不会执行,切面类还是会返回代理对象,因为aspectj会添加一个BeanPostProcessor到spring工厂,因此processor的applyBeanPostProcessorsAfterInitialization方法会返回代理对象。但是并不是最终的bean实例就是这个切面类返回的代理对象,因为可能还存在其他BeanPostProcessor来代理这个bean。
上述的观点只是个人的理解,可能存在偏差,有误的话请指出。
网友评论