spring 循环引用(依赖)的三级缓存设计原因的个人分析

本文探讨了Spring框架中循环引用的三级缓存设计原因,通过一个简单的例子说明一级缓存如何解决循环引用问题,分析了可能存在的一级缓存问题。作者指出,二级缓存增强了可读性,三级缓存则是为了优雅地处理代理对象等复杂情况。通过源码分析,揭示了三级缓存的工作机制和`doGetBean`方法的四个步骤,以及`getEarlyBeanReference`方法在处理代理对象中的作用。
摘要由CSDN通过智能技术生成

spring 循环引用(依赖)的三级缓存设计原因的个人分析

文章内容输出来源: 拉勾教育java高薪训练营

在第二模块spring源码讲解的时候,提到了这个三级缓存的设计,并且讲解了实现的过程和代码,至于为啥这么设计,却只提了一句。所谓师傅领进门,修行在个人。趁着这次跟着老师翻看源码的机会分析,带着问题去重新看源码,给出自己的一点看法,不保证一定正确,望能抛砖引玉。

1 循环引用的简单概念

有些循环引用可以解决,有些解决不了,我在这里只讨论单例模式下的属性注入。

一个最简单的循环引用,或者说依赖,A 依赖于 B,B 依赖于 A,如下

@Component
public class A {
	@Autowired
	private B b;
}

@Component
public class B {
	@Autowired
	private A a;
}

在初始化A,或者B的时候,另外一个要怎么办呢。

你可能第一反应是不太了解,也可能脱口而出说三级缓存。

确实,查了网上大多数解决方案,都是直接说用了三级缓存,至于为什么要用三级缓存,着实没有找到一个比较好的介绍。

2 为什么这么设计

2.1 分析

首先,考虑一个最简单的,只用一级缓存能不能解决问题呢?我的答案是: 可以。

还是那个简单的例子,A 依赖于 B, B 依赖于 A。

bean 初始化过程中只有一级缓存。存放的是bean.name为键,bean为值的map。

bean的创建过程是:

准备初始化bean

  1. 缓存中确认bean是否已创建(map里面拿出来,有没有)
  2. 有则返回,没有则生成一个bean,把它直接放到缓存中
  3. 填充依赖
  4. 返回bean

在这个过程下面,我们来创建A:

  1. 准备初始化A
  2. 缓存中发现没有A,实例化一个放到缓存中,当前缓存: {a:A}
  3. 填充属性,发现需要B
  1. 准备初始化B
  2. 缓存中发现没有B, 实例化一个放到缓存中 当前缓存: {a:A, b:B}
  3. 填充属性,发现需要A
  1. 准备初始化A,
  2. 发现缓存中有(当前缓存: {a:A, b:B}),返回A
  1. B初始化完成,返回
  1. A初始化完成,返回

恩,好像是那么回事,确实初始化完成了,那么,有没有问题呢?

2.2 可能的问题

  1. B在初始化过程中,拿到的A是一个正在创建中的A,也就是说A还没创建完。这个有影响吗?

我觉得没有。因为不管怎么样,B在想要拿到A的引用的时候,A都不可能初始化完成,只能拿到A的引用,毕竟B就是在A创建过程中创建的。

而且,我们甚至可以缓存一个包装对象,把这个bean的创建信息也一起缓存起来,就能区分是否正在创建了。

  1. 如果说A不是一个普通对象,而是一个代理对象。这个有影响吗?

我认为也没有。在A实例化的过程中,如果发现A需要变成一个代理对象,那就把实例化之后的代理对象放进去,最后也能完成。

ps: 我在最后附带了一个单map思路可用代理循环依赖的简单实现,欢迎提出问题

2.3 总结

ok, 既然一级缓存就解决了,为啥还要三级缓存呢?

我个人的理解是一种设计,二级是为了可读性更强,三级是为了更优雅的处理代理对象等情况(后面的源码解析也加深了我的这个观点)。

我说二级是为了可读性更强,是因为,一级确实能够区分,比如加个包装(虽然又带来了多余的类)
来看下面的代码:

	//case 1
	Map<String, Object> allBean;
	//获取已经加载的bean
	allBean.forEach((name, val) -> {
			WrapBean wrapBean = (WrapBean) val;
			if (wrapBean.info.isLoad){
				Object bean = wrapBean.bean
				//doXX
			}
		});
	//initBean 获取类似,然后对比下面的代码
	
	//case 2
	Map<String, Object> loadBean;
	Map<String, Object> initBean; 
	//获取已经加载的bean
	// dosomthing on loadBean...

对吧,一个map完全能够胜任,但是他干的不是很好。

至于3级,我们来考虑一个情况。

@Component
@EnableTransactionManagement
public class A {
	@Autowired
	private B b;

	@Transactional
	public void hi(){
		System.out.println("i am a!");
	}
}

@Component
public class B {
	@Autowired
	private A a;
	public void hi(){
		System.out.println("i am b!");
	}
}

在这个情况下面,A需要生成一个代理对象才能满足事务要求,而B还是那个普通对象。 考虑上面的bean生成过程第二步,在

2 有则返回,没有则生成一个bean,把它直接放到缓存中

这个过程中,生成一个bean得判断对A是否增强,相当于对A进行了一波分析。

这个没有问题,但是,考虑到我们正在创建一个bean的时候,突然考虑要不要代理好像有点奇怪。虽然我们确实要考虑,但是这个时间点是不是太靠前了?

如果说我提供一个勾子,允许某些后置处理器对bean进行改动后把原来的bean替换掉,在需要的时候再提供代理而且以后还可以根据需要动态增加多层代理,这个设计是不是更好一些呢?

恩,这个设计在我的眼中就是三级缓存。

3 spring 中的三级缓存源码分析

说到底,在spring中,这个三级具体是怎么工作的呢。还是以上面的A,B为例子,其中,A将会产生代理对象,也就是说假设A进行了AOP增强。

根据实际情况来看,第三级暴露的是一个工厂方法,这里我们先把他们叫做 getA 和 getB.

getA和getB是一个bean最开始的时候暴露自己提供的工厂方法,不仅能代理,甚至还能多层代理,修改bean的原始对象。

那么,究竟是怎么做到的呢? 进入源码分析分析。

3.1 三级缓存具体是什么

首先来看所谓的三级缓存,他存在

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

这个类下面,就是三个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 HashMap<>(16);

其中:

  1. singletonObjects 代表第一级,已经实例化完成的bean
  2. earlySingletonObjects 代表第二级, 被别人处理过的bean
  3. singletonFactories 代表第三级,最开始暴露的一级,暴露的是一个工厂方法,其他bean通过这个方法来拿

拿缓存的过程就是,先1后2再3

既然这次我们的关注目标都在bean上,context 的流程refresh那些先不说,直接来到获取bean的方法这儿。

3.2 doGetBean

第一个方法:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

获取所有的bean的外层接口,部分源码如下。

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.
		// 1
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {/* omit logs */}
			//一些处理
			// 2
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}else{
			//omit...
			// Create bean instance.
				if (mbd.isSingleton()) {
					// 3
					sharedInstance = getSingleton(beanName, () -> {
						try {
							// 4
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
				//omit...
		}
		//omit
		return (T) bean;
	}

我重点标了四个步骤

3.2.1 步骤一

从getSingleton里拿缓存, 过程就是 123 级挨着找,allowEarlyReference 参数控制要不要从第三级找。最开始传过来就是true, 如果是false 的话就不会从三级里面找,这个有啥用呢?先把这个问题放着。

从三级里面找到了,就调用ObjectFactory.getObject 获取,然后放到二级缓存里面,ObjectFactory.getObject也留着后面分析。

需要注意的是,从代码可以看出,三级缓存放的是 ObjectFactory对象,而二级缓存放的是ObjectFactory.getObject()返回的对象,二者可能不同。

ObjectFactory.getObject() 就是上面的 getA 和 getB,很顺利的找到调用点了,但是具体调用的是个什么东西还得接着看。

	public Object getSingleton(String beanName) {
		return getSingleton(beanName, true);
	}
	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;
	}
3.2.2 步骤2

这个函数是返回前做的一些后置处理,没多大影响,返回的地方基本都有。缓存找到了,咱就返回了。

protected Object getObjectForBeanInstance(
			Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

		// Don't let calling code try to dereference the factory if the bean isn't a factory.
		if (BeanFactoryUtils.isFactoryDereference(name)) {
			if (beanInstance instanceof NullBean) {
				return beanInstance;
			}
			if (!(beanInstance instanceof FactoryBean)) {
				throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
			}
		}

		// Now we have the bean instance, which may be a normal bean or a FactoryBean.
		// If it's a FactoryBean, we use it to create a bean instance, unless the
		// caller actually wants a reference to the factory.
		if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
			//一般这里就返回了
			return beanInstance;
		}
		//omit...
	}
3.2.3 步骤3

既然缓存没找到,那就创建一个,创建的过程顺便把缓存也存起来. 这里传入的ObjectFactory对象,负责具体创建。
也就是第四步的工作, 等一下再看。直接进入新的getSingleton 重载方法

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				//omit 一些判断
				try {
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				//omit catch and finaly
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}
	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);
		}
	}

整个代码比较简单

一系列判断完成之后,调用第四步的工厂创建, 接着
1级缓存 插入!
2级缓存 删除!
3级缓存 删除!
已经注册的bean 增加!
done.

总结, 调用ObjectFactory 生成 bean 后加入缓存。所以重点还是在第四步

3.2.4 步骤4

步骤4最终是调用的是下面的方法

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean

然鹅,这个方法经过了一系列的判断实际来到了 doCreateBean 方法

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			//1 实例化普通bean
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		// 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) {
		//omit logs
			// 2 暴露自己的工厂方法
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			// 3
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		//omit catch
		if (earlySingletonExposure) {
			// 4
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				//omit dependence
				}
			}
		}
//omit
		return exposedObject;
	}

又有4个主要步骤

  1. 直接实例化普通bean。

  2. 第二,暴露自己的工厂方法

哦呼,getA! 三级缓存的优雅实现,到现在终于找到了!那么他究竟干了什么呢?
可以看到实际上调用的是下面这个方法

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}


emmm, 怎么说呢,稍微有点普通,不太符合预期。竟然就是简单的遍历了一下bean的后置处理器,找到所有的SmartInstantiationAwareBeanPostProcessor, 然后执行他的 getEarlyBeanReference 方法。

从名字看, 他是一个比较聪明的处理器
进入 SmartInstantiationAwareBeanPostProcessor 看看注释,英文?并不怕,translate.google.cn open!

这个接口总结说,它主要在框架内部使用。
getEarlyBeanReference 方法呢,就是设计来给 post-processors 一个早点暴露wrapper 的机会,这个rapper 我的理解就是代理。

	/**
	 * Obtain a reference for early access to the specified bean,
	 * typically for the purpose of resolving a circular reference.
	 * <p>This callback gives post-processors a chance to expose a wrapper
	 * early - that is, before the target bean instance is fully initialized.
	 * The exposed object should be equivalent to the what
	 * {@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization}
	 * would expose otherwise. Note that the object returned by this method will
	 * be used as bean reference unless the post-processor returns a different
	 * wrapper from said post-process callbacks. In other words: Those post-process
	 * callbacks may either eventually expose the same reference or alternatively
	 * return the raw bean instance from those subsequent callbacks (if the wrapper
	 * for the affected bean has been built for a call to this method already,
	 * it will be exposes as final bean reference by default).
	 * <p>The default implementation returns the given {@code bean} as-is.
	 * @param bean the raw bean instance
	 * @param beanName the name of the bean
	 * @return the object to expose as bean reference
	 * (typically with the passed-in bean instance as default)
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

虽然有点失落,不过处理方式确实就是这样了。

  1. 填充属性

填充属性的过程中还有挺多的操作,会再次调用getBean递归处理依赖,中间也有其他的一些processor参与,限于篇幅和主题就不展开了。

  1. 从1,2级缓存里面拿

恩,这里传入的值就是false,只需要从1,2级里面拿缓存。将原始的bean替换为被 SmartInstantiationAwareBeanPostProcessor改过的bean。

3.3 总结

回到A的初始化上面来看。

  1. 初始化A的时候,发现123都不存在,就创建一个普通的A对象a, 接着在第三级暴露一个工厂方法,假设这里是getA
  2. 填充A的属性,发现需要B, 发现123都不在, 创建一个普通的B对象b,第三级暴露 getB
  3. 填充B的属性,发现需要A,第三级存在,调用getA。 这里可能会返回代理对象, 注意,如果返回了代理对象,那么整个过程创建了两个A,一个普通,一个代理,实际上可能还不止,取决于getA里面的后置处理器。
  4. B填充完,又从12(没有3)级里面再去找B,发现没有,返回b
  5. A填充完,又从12级找A,发现二级有,那么返回二级里面的A,丢弃a

有两个重要的地方

  1. getA 和 getB 代表了三级缓存存在的意义,修改了原始bean对象
  2. bean属性填充完成之后又从1,2级找是否有代理,这时不从3里找

4 附录 单缓存依赖注入

4.1 主代码

package test;

import net.sf.cglib.proxy.*;

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SuperSpring {

    protected Map<String, Object> allCache = new ConcurrentHashMap<String, Object>();

    public SuperSpring() { }

    public <T> T getBean(Class<T> clazz) {
        String name = clazz.getName();
        T bean = (T) allCache.get(name);
        if (bean != null) { return bean; }

        try {
            bean = clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("new instance err " + name + e);
        }

        bean = expose(name, bean);

        populate(name, bean);

        return bean;
    }
    
    protected <T> T expose(String name, T bean) {
        if (bean.getClass().isAnnotationPresent(Proxy.class)) {
            /* cglib 增强 */
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(bean.getClass());
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
                Object result = methodProxy.invokeSuper(o, objects);
//                System.out.println(String.format("%s.%s invoked by proxy", o.getClass().getName(), method.getName()));
                return result;
            });
            enhancer.setClassLoader(bean.getClass().getClassLoader());
            bean = (T) enhancer.create();
        }
        allCache.put(name, bean);
        return bean;
    }

    private void checkField(Object o, Field field) throws IllegalAccessException {
        if (field.getAnnotation(Autowire.class) != null) {
            Class<?> type = field.getType();
            String name = type.getName();
            Object bean = allCache.get(name);
            if (bean == null) {
                bean = getBean(type);
            }
            field.setAccessible(true);
            field.set(o, bean);
        }
    }

    protected void populate(String name, Object bean) {
        Field[] fields;
        //判断是不是proxy
        if (bean instanceof Factory) {
            fields = bean.getClass().getSuperclass().getDeclaredFields();
        } else {
            fields = bean.getClass().getDeclaredFields();
        }

        for (Field field : fields) {
            try {
                checkField(bean, field);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Create Bean err : " + name + e);
            }
        }
    }
    @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented
    public @interface Proxy { }

    @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented
    public @interface Autowire { }
}


4.2 测试代码

package test;

public class TestSuper {

    @SuperSpring.Proxy
    public static class A{
        @SuperSpring.Autowire
        private B b;
        @SuperSpring.Autowire
        private A a;
        
        public void hi(){
            System.out.println("hi, i'm a!");
        }

        public B getB() {
            return b;
        }

        public A getA() {
            return a;
        }
    }
    public static class B{
        @SuperSpring.Autowire
        private A a;
        @SuperSpring.Autowire
        private B b;
        //no autowire
        private A a2;
        public void hi(){
            System.out.println("hi, i'm b!");
        }

        public A getA() {
            return a;
        }

        public B getB() {
            return b;
        }

        public A getA2() {
            return a2;
        }
    }

    public static void main(String[] args) {
        SuperSpring spring = new SuperSpring();
        A a = spring.getBean(A.class);
        B b = spring.getBean(B.class);
        
        a.hi();
        b.hi();
        a.getB().hi();
        b.getA().hi();
        System.out.println(a.getB() == b);
        System.out.println(b.getA() == a);
        System.out.println(a.getB().getA() == a);
        System.out.println(b.getA().getB() == b);
        System.out.println(a.getClass());
        System.out.println(b.getClass());

        b.getB().hi();
        System.out.println(b.getB() == b);
        System.out.println(b.getA2());

        a.getA().hi();
        System.out.println(a.getA() == a);
    }
}

输出结果

hi, i'm a!
hi, i'm b!
hi, i'm b!
hi, i'm a!
true
true
true
true
class test.TestSuper$A$$EnhancerByCGLIB$$512425b1
class test.TestSuper$B
hi, i'm b!
true
null
hi, i'm a!
true
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值