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 默认已经不允许循环引用了,必须手动开启。

相关推荐
netyeaxi6 小时前
Java:使用spring-boot + mybatis如何打印SQL日志?
java·spring·mybatis
小七mod7 小时前
【MyBatis】MyBatis与Spring和Spring Boot整合原理
spring boot·spring·mybatis
程序猿小D9 小时前
[附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+jsp实现的个人财务管理系统,推荐!
java·数据库·mysql·spring·毕业论文·ssm框架·个人财务管理系统
永日4567011 小时前
学习日记-spring-day42-7.7
java·学习·spring
二十雨辰11 小时前
[尚庭公寓]07-Knife快速入门
java·开发语言·spring
NE_STOP13 小时前
SpringBoot--简单入门
java·spring
张小洛15 小时前
Spring AOP 设计解密:代理对象生成、拦截器链调度与注解适配全流程源码解析
java·后端·spring·spring aop·aop
Wyc7240916 小时前
SpringBoot
java·spring boot·spring
neoooo17 小时前
别慌,Java只有值传递——一次搞懂“为啥我改了它还不变”!
java·后端·spring
GJCTYU19 小时前
spring中@Transactional注解和事务的实战理解附代码
数据库·spring boot·后端·spring·oracle·mybatis