Spring循环引用和三级缓存

前言

Spring 解决 Bean 之间的循环引用关系用到了三级缓存,那么问题来了。三级缓存是怎么用的?每一层的作用是什么?非得用三级吗?两级缓存行不行?

理解循环引用

所谓的"循环引用"是指 Bean 之间的依赖关系形成了一个循环,例如 a 依赖 b,b 又依赖 a。

java 复制代码
@Component
public class A {
  @Autowired
  B b;
}

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

开发者在设计阶段,应该尽量避免出现循环引用,因为这种依赖关系本身就违反了"单一职责"的设计原则,增加了代码的复杂性和维护成本。

抛开设计原则不谈,循环引用给 Spring 带来了依赖注入的问题 。在这个例子中,Spring 实例化 a 时发现要为其注入 b,于是赶忙去实例化 b,实例化 b 的过程中又发现要为其注入 a,但此时 a 还没创建完,陷入一个死循环,就像死锁一样。

为什么多级缓存可以解决循环引用的问题呢?

理解多级缓存

Spring 里的三级缓存其实就是三个 Map 容器,先抛开 Spring 不谈,如果让我们设计一个多级缓存方案来解决循环引用的问题,我们会怎么做呢?

一级缓存

一级缓存的作用是为了保证单例。在 Spring 里面,Bean 默认是单例的,多次调用getBean() 得到的是同一个实例。基于这个规则,我们可以用一级缓存来实现一个保证单例的 IOC 容器。

java 复制代码
public class Ioc1 {

    private final Map<Class, Object> singletonObjects = new ConcurrentHashMap<>();

    public synchronized <T> T getBean(Class<T> clazz) {
        Object bean = singletonObjects.get(clazz);
        if (bean == null) {
            singletonObjects.put(clazz, bean = createBean(clazz));
        }
        return (T) bean;
    }

    @SneakyThrows
    private <T> Object createBean(Class<T> clazz) {
        T bean = clazz.newInstance();
        return bean;
    }
}

二级缓存

一级缓存只能保证单例,对于循环引用是没办法解决的。因此,我们可以再加一级缓存,引入二级缓存来解决循环引用问题。于是,我们实现了第二版 IOC 容器,核心思路是把未被初始化的 bean 提前暴露到二级缓存,依赖注入时允许注入半成品 bean

java 复制代码
public class Ioc2 {
    private final Map<Class, Object> singletonObjects = new ConcurrentHashMap<>();
    private final Map<Class, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    public synchronized <T> T getBean(Class<T> clazz) {
        Object bean = singletonObjects.get(clazz);
        if (bean == null) {
            bean = earlySingletonObjects.get(clazz);
            if (bean == null) {
                singletonObjects.put(clazz, bean = createBean(clazz));
            }
        }
        return (T) bean;
    }

    @SneakyThrows
    private <T> Object createBean(Class<T> clazz) {
        T bean = clazz.newInstance();
        // 把未被初始化的bean提前暴露到二级缓存
        earlySingletonObjects.put(clazz, bean);
        populateBean(bean);
        return bean;
    }

    // 属性注入
    @SneakyThrows
    private <T> void populateBean(T bean) {
        for (Field field : bean.getClass().getDeclaredFields()) {
            Object fieldValue = getBean(field.getType());
            field.setAccessible(true);
            field.set(bean, fieldValue);
        }
    }
}

三级缓存

大多数情况下,二级缓存已经够用了,但是 Spring 还有一项强大的功能:基于 Bean 生成代理对象做增强。

此时,用二级缓存就面临一个问题:代理对象何时生成?

  • 如果等 Bean 初始化后再生成 Proxy,那已经被注入的属性却是个未被代理 Bean,这显然是不能接受的
  • 如果 Bean 初始化前就提前生成代理对象,这样能保证注入的属性也是被代理的 Bean,但是这不符合 Spring 的设计原则

为什么提前生成代理对象不符合 Spring 的设计原则呢???

因为在 Spring 的 Bean 和 AOP 的生命周期里,应该是先实例化并初始化 Bean 以后,再调用 BeanPostProcessor 的子类 AbstractAutoProxyCreator 的后处理器方法来生成代理对象。提前基于未被初始化的半成品 Bean 生成代理对象,这一点违背了 Spring 的设计原则,后处理器理应认为要扩展的 Bean 是一个完整的 Bean,万一需要访问其属性,拿到的却是 null,可能导致程序错误。

所以,我们可以得出一个结论:Spring 循环引用的问题,只用二级缓存是完全没问题的,前提是要提前生成代理 Bean 并暴露到二级缓存。功能是没问题,但是这一点违背了 Spring 的设计原则,所以 Spring 要尽量避免这个问题,才引入的三级缓存。

Spring 给出的方案是:引入一个三级缓存,尽可能避免提前创建代理对象,万一真的发生了循环引用,不得已而为之,也只能提前生成了。

所以,我们现在可以给出一个最终版的 IOC 实现了,逻辑基本和 Spring 一致。核心思路是,Bean 实例化以后,提前暴露一个 ObjectFactory 到三级缓存,如果没有循环引用,代理对象不会提前创建,Bean 的生命周期保持一致。如果发生了循环引用,就只能提前创建代理对象,并把它从三级缓存挪到二级缓存,避免重复创建,其它 Bean 注入的也是被代理后的 Bean。

java 复制代码
public class Ioc3 {
    private final Map<Class, Object> singletonObjects = new ConcurrentHashMap<>();
    private final Map<Class, Object> earlySingletonObjects = new ConcurrentHashMap<>();
    private final Map<Class, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>();
    // 代理对象缓存
    private final Map<Class, Object> proxyCache = new ConcurrentHashMap<>();

    public synchronized <T> T getBean(Class<T> clazz) {
        Object bean = singletonObjects.get(clazz);
        if (bean == null) {
            bean = earlySingletonObjects.get(clazz);
            if (bean == null) {
                ObjectFactory<?> objectFactory = singletonFactories.get(clazz);
                if (objectFactory != null) {
                    bean = objectFactory.getObject();
                    earlySingletonObjects.put(clazz, bean);
                    singletonFactories.remove(clazz);
                } else {
                    bean = createBean(clazz);
                    singletonObjects.put(clazz, bean);
                }
            }
        }
        return (T) bean;
    }

    @SneakyThrows
    private <T> Object createBean(Class<T> clazz) {
        T bean = clazz.newInstance();
        singletonFactories.put(clazz, () -> wrapIfNecessary(bean));
        populateBean(bean);
        return wrapIfNecessary(bean);
    }

    private <T> T wrapIfNecessary(final T bean) {
        if (true) {
            Object proxy = proxyCache.get(bean.getClass());
            if (proxy == null) {
                proxy = ProxyFactory.getProxy(bean.getClass(), new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                        if (method.getDeclaringClass().isAssignableFrom(Object.class)) {
                            return method.invoke(this, args);
                        }
                        System.err.println("before...");
                        Object result = method.invoke(bean, args);
                        System.err.println("after...");
                        return result;
                    }
                });
                proxyCache.put(bean.getClass(), proxy);
            }
            return (T) proxy;
        }
        return bean;
    }

    @SneakyThrows
    private <T> void populateBean(T bean) {
        for (Field field : bean.getClass().getDeclaredFields()) {
            Object fieldValue = getBean(field.getType());
            field.setAccessible(true);
            field.set(bean, fieldValue);
        }
    }
}

Spring三级缓存实现

Spring 三级缓存对应的 Map 声明在 DefaultSingletonBeanRegistry 类,如下:

java 复制代码
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

一级缓存 singletonObjects:存放完整的,初始化后的 Bean

二级缓存 singletonFactories:存放未被初始化的半成品 Bean

三级缓存 earlySingletonObjects:存放 Bean 对应的 ObjectFactory,用于提前生成代理对象

在获取单例 Bean 时,Spring 会先查找一级和二级缓存,都没有时再查找三级缓存,如果找到了,就提前创建代理对象并返回。

java 复制代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 查一级缓存
	Object singletonObject = this.singletonObjects.get(beanName);
	// 没有且Bean在创建中
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// 查二级缓存取
		singletonObject = this.earlySingletonObjects.get(beanName);
		// 没有且允许引用半成品Bean
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// double check
				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;
}

如果不存在循环引用,ObjectFactory#getObject 就不会被调用,也就不会提前创建代理对象。

doCreateBean() 方法里,会提前把未被初始化的半成品 Bean 封装成 ObjectFactory 暴露到三级缓存:

java 复制代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
  ......
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	......
}

getObject() 方法也就是getEarlyBeanReference() ,它会调用后处理器SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 生成代理对象(未必所有的Bean都需要被代理),目前只有一个实现类 AbstractAutoProxyCreator:

java 复制代码
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}

尾巴

Spring 为了解决循环引用的问题,设计了三级缓存。一级缓存的作用是保证单例;二级缓存的作用是解决循环引用;三级缓存是为了尽量避免基于半成品 Bean 提前创建代理对象。单从功能上说,只用二级缓存完全没问题,前提是被代理的 Bean 都要提前创建代理对象,这一点违背了 Spring 的设计原则。在 Spring Bean 的生命周期里,应该是先实例化并初始化 Bean 后再通过后处理器进行扩展,对一个未被初始化的 Bean 做扩展,万一要访问其属性可能就会导致程序错误。Spring 的做法是提前暴露一个 ObjectFactory 对象,如果没有发生循环引用,其getObject() 就不会被调用,代理对象就不会提前生成,尽可能的保证 Bean 生命周期一致。

但是,如果真的发生了循环引用,且引用的 Bean 又是需要被代理的,也只能提前生成代理对象了。因为这么做违背了 Spring 的设计原则,所以新版本的 Spring 默认已经不允许循环引用了,必须手动开启。

相关推荐
不会吃萝卜的兔子2 小时前
spring微服务宏观概念
java·spring·微服务
Chan162 小时前
流量安全优化:基于 Nacos 和 BloomFilter 实现动态IP黑名单过滤
java·spring boot·后端·spring·nacos·idea·bloomfilter
顾漂亮6 小时前
Redis深度探索
java·redis·后端·spring·缓存
努力也学不会java6 小时前
【Spring】Spring事务和事务传播机制
java·开发语言·人工智能·spring boot·后端·spring
IT·陈寒6 小时前
从 Spring 到 SpringBoot,再到 SpringAI:框架的进化与思考
java·spring boot·spring
知其然亦知其所以然7 小时前
一次JPA联表查询,竟让我服务器无限循环崩溃?!
java·后端·spring
我命由我123457 小时前
Spring Cloud - Spring Cloud 负载均衡(Ribbon 负载均衡概述、Ribbon 使用)
java·后端·spring·spring cloud·ribbon·java-ee·负载均衡
DokiDoki之父8 小时前
Spring—容器
java·后端·spring
带刺的坐椅15 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
壹佰大多19 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring