前言
1、spring循环依赖是什么?
2、spring创建bean的过程中的循环依赖
3、循环依赖的解决
4、三级缓存的作用
第一级缓存
第二级缓存
第三级缓存
总结
本文是根据学习了腾讯课堂图灵学院的spring循环依赖学习视频,结合自己的理解所写,主要对自己所写的东西做个记录,方便以后回忆,可能存在不正确的地方,如有问题请多多指教。学习视频链接如下:BAT大厂高频面试题 Spring循环依赖底层原理深度剖析【图灵课堂】-学习视频教程-腾讯课堂https://ke.qq.com/course/3067506
简单来说就是类A中需要类B, 类B中需要类A。以OrderSerive和UserService为例。
package com.spring.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderService {
@Autowired
private UserService userService;
public void say() {
System.out.println("order say");
}
}
package com.spring.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UserService {
@Autowired
private OrderService orderService;
public void say() {
System.out.println("user say");
}
}
package com.spring.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.spring.test")
public class TestSpring {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestSpring.class);
System.out.println("here:");
OrderService orderService = applicationContext.getBean(OrderService.class);
System.out.println(orderService.getClass());
orderService.say();
}
}
package com.spring.test.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.spring.test.*.*(..))")
public void before(){
System.out.println("before");
}
}
spring创建bean的过程大概可以分为四步。
现在以以OrderSerive和UserService为例,来简单模拟下spring创建bean的过程。
1. 实例化OrderService,得到对象,OrderService orderService=new OrderService(),spring实际不是简单的new出来,这里只是简单模拟实例化这个动作。
2. OrderService的属性UserService赋值,需要UserService对象。
2. 1 实例化UserService,得到对象,UserService userService=new UserService。一种递归方式的调用。
2.2 UserSerivce的属性OrderService赋值,需要OrderService对象。此时发现,对于这种递归调用,如果再走第1步实例化OrderService,就会陷入死循环。
从上面的spring创建bean的过程发现,只要保证OrderService在2.2步骤中不去创建,而是直接拿到1步骤中实例化的对象就不会有死循环的问题,而解决这个问题的关键只要把OrderService实例化的对象放到一个缓存中,在2.2中能直接获取到就可以了。
实际上spring解决循环依赖死循环的问题也确实是通过缓存来解决。来看下spring的关键代码。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new HashMap<>(16);
/**
* Return the (raw) singleton object registered under the given name.
* Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
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;
}
}
此代码只截取了org.springframework.beans.factory.support.DefaultSingletonBeanRegistry中的部分代码。从getSingleton方法注释能看出,此方法允许返回一个正在创建中的早期引用的singleton来解决循环依赖问题。
singletonObjects 就是第一级缓存,也是我们的spring ioc容器的单例池,里面存储了单例bean。 earlySingletonObjects 就是第二级缓存,存储了实例化后未完成属性赋值及初始的单例bean,可以称为不完整单例bean。 singletonFactories 就是第三级缓存,存储了实例化后未完成属性赋值及初始的单例bean的bean工厂,可以对不完整bean进行加工。
我们先以OrderService和UserService为例,AbstractBeanFactory#doGetBean方法为入口,来梳理下spring bean的创建过程,以及循环依赖如何解决。
1. 创建OrderSerivce, 调用getSingleton(beanName),此时三级缓存都为空的,获取不到。然后调用getSingleton(beanName, ObjectFactory),看下这个方法的注释,这个方法是可以创建和注册新的bean,而且创建过程中加同步锁了的。调用beforeSingletonCreation(beanName); 将OrderService标记为正在创建中,调用singletonFactory.getObject(),实际调用的是AbstractAutowireCapableBeanFactory#createBean,然后再调用doCreateBean,再调用createBeanInstance,此方法就是实例化得到不完整OrderSerivce bean。如果是单例、允许循环引用、正在创建中,以上条件都是满足的,一般来说我们自定义的所有bean都是满足这些条件的,都是会调用addSingletonFactory,将不完整OrderService bean,放入三级缓存,移除二级缓存。我们会发现二级缓存和三级缓存不会存在同一个bean,一个放入,另一个会移除,而且放入加移除的动作一起是加锁了的。而三级缓存中放入的是一个lambda表达式,其实我们可以理解为一个实现了ObjectFactory接口的匿名类的对象,这个对象里面放入了不完整OrderService bean。实际上在基于不支持lambda表达式jdk版本中的spring就是用匿名类对象。
2. OrderService属性赋值,调用populateBean(beanName, mbd, instanceWrapper),在赋值过程中,需要UserService,第二次来到getSingleton(beanName),然后从第一级缓存singletonObjects中获取不到,但是UserService并不是正在创建中的bean,所以会走创建逻辑。
2.1 创建UserService,实例化UserService得到不完整的UserService bean,放入三级缓存中,移除二级缓存。创建逻辑跟第一步创建OrderService是一致的。
2.2 UserService属性赋值,在赋值过程中,需要OrderService,第三次来到getSingleton(beanName),然后从第一级缓存singletonObjects中获取不到,但是发现OrderService是正在创建的bean。这里我们就可以发现,这里如果是正在创建的bean,那必然是循环依赖中非首次创建bean了。接着再去二级缓存earlySingletonObjects找orderService,还是没有,再去三级缓存singletonFactories找,就可以拿到第一次创建OrderService放置的不完整bean。这里拿到了一个beanFactory,是因为不完整的bean可能需要加工下才能使用,比如orderservice是需要实现AOP的,而实现AOP是会拿到一个代理对象bean,而不是本身bean。所以beanFactory可以对原始不完整的bean提前进行AOP处理(正常AOP是需要在初始化中处理)。因为如果不提前进行AOP处理,那UserService对象中的属性OrderService bean就不是一个代理对象,不能实现AOP的功能。如果OrderService不需要AOP,那么直接拿到原始不完整OrderService bean。同时需要放入二级缓存,移除三级缓存。这里从三级缓存挪到二级缓存,就可以保证OrderService bean只会被加工一次。这里属性赋值拿到了OrderService bean后就可以完成UserService的属性赋值了。
2.3 UserService初始化。如果UserService也需要AOP,那么这一步就将原始UserService bean转为UserService代理bean。
2.4 UserService完成初始化后,可以说变成了完整的bean。然后又会调用getSingleton(beanName, false),这里allowEarlyReference指定为false,就不会去三级缓存查找,只会到一级、二级缓存查找。而一级、二级缓存是没有UserService的。最后将完整的UserService bean放入一级缓存单例池singletonObjects,从二级、三级缓存中移除。
UserService完成创建后,就继续回到OrderService属性赋值过程中,OrderService就拿到了UserService bean完成OrderService的属性赋值。
3. OrderService初始化。实现AOP的后置处理器,AbstractAutoProxyCreator.postProcessAfterInitialization中,会发现earlyProxyReferences中保存的OrderServiceBean和要初始化的OrderService bean是同一个,说明orderService bean已经做过一次AOP了,就不会再次AOP了,就不会调用wrapIfNecessary。实际我们在三级缓存转二级缓存过程调用的BeanFactory就是执行了wrapIfNecessary。所以这里就不会再次执行了。完成初始化动作后,又会调用getSingleton(beanName, false),这里allowEarlyReference指定为false,就不会去三级缓存查找,只会到一级、二级缓存查找。在二级缓存查找到了OrderService bean,这个bean是已经执行过AOP的代理bean,然后替换原始的bean。这里就得到了完整的OrderService bean。这里我们可以发现,OrderService是先实例化得到原始bean,再将原始bean提前AOP得到代理bean,再将原始bean属性赋值,这里原始bean赋值后,因为AOP的代理bean中持有的原始bean是同一个对象,实际也已经赋值了,而代理bean是不会属性赋值的。最后再将AOP代理bean替换原始bean返回,放入一级缓存单例池。
spring循环依赖加载过程https://www.processon.com/embed/62970bcfe401fd2eed1bebc9
/** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new ConcurrentHashMap<>(256);
单例池,保存了所有完整的spring bean,ConcurrentHashMap保证了线程安全。
/** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new HashMap<>(16);
保存了不完整的早期spring bean。主要是为了区分完整的bean和不完整的bean,同时提升获取bean的性能。如果不使用二级缓存,把完整和不完整的bean都放到一级缓存中,这样也是可以解决循环依赖问题的,但是这样会存在一个问题,比如主线程ThreadMain刷新spring容器中的bean,正在创建OrderService,把OrderService不完整的bean放到一级缓存中提前暴露出去,然后开始属性赋值,这时来了一个新的线程Thread1需要从一级缓存中获取OrderService,这时就会取到不完整的 OrderService bean。这就出问题了。 但试想下,就算我加上二级缓存,新的线程Thread1还是可以从二级缓存中获取到不完整的OrderService bean。所以要解决线程Thread1获取到不完整的OrderService bean的关键,还是得加锁,如果只有一级缓存,那开始创建OrderService的时候,我就得把一级缓存锁住,不让别的线程访问一级缓存。但这样也有问题,你创建OrderService,但是Thread1获取UserService,这也不让获取那效率就太低。这时我加上一个二级缓存,效果就不一样了。如开始创建OrderService,加锁,将不完整的OrderService bean放入二级缓存,属性注入时创建UserService,UserService完成属性赋值和初始化后放入一级缓存,OrderService也完成属性注入,开始初始化。这个时候OrderService初始化过程阻塞了,但是UserService实际已经创建完并放入一级缓存了。这是线程Thread1来了,获取UserService,从一级缓存中直接可以获取到UserService,跟OrderService加锁后阻塞实际没有关系。这样任何线程都可以从一级缓存中获取bean。只有当你从一级缓存中获取不到bean,需要加锁获取二级缓存时,发现OrderService创建的线程已经加锁了,那我就只能等着OrderService创建完成后释放锁再访问二级缓存了。下面以实际代码举例,将之前的OrderService和TestSpring稍微改动验证下。
package com.spring.test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderService implements InitializingBean {
@Autowired
private UserService userService;
public void say() {
System.out.println("order say");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(TestSpring.getNow()+" OrderService afterPropertiesSet start...........");
//OrderService初始化时睡眠1分钟,导致创建过程中阻塞住
Thread.sleep(60000);
System.out.println(TestSpring.getNow()+" OrderService afterPropertiesSet end ...............");
}
}
package com.spring.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.text.SimpleDateFormat;
import java.util.Date;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.spring.test")
public class TestSpring {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(TestSpring.class);
new Thread(() -> applicationContext.refresh()).start();
Thread.sleep(2000);
System.out.println(TestSpring.getNow() + " get bean start.........");
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(TestSpring.getNow() + " get bean end.........");
System.out.println(TestSpring.getNow() + " userService "+userService.getClass()+".........");
}
public static String getNow() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}
从运行结果可以看到,OrderService初始化调用afterPropertiesSet过程中,获取UserService的bean是可以正常获取到的。我们把main方法中获取UserService的代码改成OrderService,再看看运行结果。
package com.spring.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.text.SimpleDateFormat;
import java.util.Date;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.spring.test")
public class TestSpring {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(TestSpring.class);
new Thread(() -> applicationContext.refresh()).start();
Thread.sleep(2000);
System.out.println(TestSpring.getNow() + " get bean start.........");
OrderService orderService = applicationContext.getBean(OrderService.class);
System.out.println(TestSpring.getNow() + " get bean end.........");
System.out.println(TestSpring.getNow() + " userService "+orderService.getClass()+".........");
}
public static String getNow() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}
从结果中可以看到,只有当OrderService的afterPropertiesSet方法执行完后,OrderService创建完后释放锁,main线程才从spring容器中获取到了OrderService bean。下面再来看看源码是如何实现的。getSingleton创建bean时,将整个创建过程加锁,锁对象是singletonObjects。
/**
* Return the (raw) singleton object registered under the given name,
* creating and registering a new one if none registered yet.
* @param beanName the name of the bean
* @param singletonFactory the ObjectFactory to lazily create the singleton
* with, if necessary
* @return the registered singleton object
*/
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
//创建bean时,将对象singletonObjects加锁
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
创建bean之前从三级缓存获取时,是可以直接从第一级缓存singletonObjects,当第一级缓存获取不到,且是正在创建的bean时,先尝试获取锁,锁对象是singletonObjects,如果其他线程正在创建bean已经锁住,那就只能先等待了。所以创建bean过程加锁后,其他线程就不会获取到不完整的bean了。
/**
* Return the (raw) singleton object registered under the given name.
* Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//访问二级、三级缓存前,先加锁,锁对象是singletonObjects
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;
}
在分析完bean的加载过程时,可以发现每次创建bean后,都会将bean放入三级缓存中,而且放入的是存储了bean和其他信息的一个lambda表达式。这里最主要的作用就是为了能提前AOP作准备,只有存在循环依赖时,从第三级缓存中获取到lambda表达式,就可以直接调用实现AOP,获取代理对象放到二级缓存了。如果没有第三级缓存,直接使用第二级缓存保存lambda表达式,会怎么样了。这时会有这样的问题,UserService创建过程中发现需要OrderService,从三级缓存拿到OrderService的beanFactory,执行一次AOP,如果OrderService有MemberService也需要注入,而MemberService属性中也有OrderService,第二次循环依赖,创建MemberService,创建过程中也需要OrderService,从三级缓存再次拿到orderService的beanFactory,又执行一次AOP。这样就会导致执行两次AOP了。实际spring是会判断有没有执行过AOP,不会执行2次,但是执行判断逻辑是需要2次的。为了区分开持有原始bean的beanFactory 和 执行过beanFactory的代理bean。(如果不需要AOP,执行过beanFactory的原始bean,依然还是原始bean),用二级缓存+三级缓存区分开来,对程序可读性、可维护性、解耦啥的都是有帮助的。
spring循环依赖解决的核心就是将创建过程中早期不完整的bean提前暴露出去,这样别的bean可以直接引用这个bean,不需要再次创建。
而二级缓存、三级缓存,是对解决循环依赖中出现的其他问题补充。
所以说三级缓存并不是解决循环依赖所必须的,应该说是解决循环依赖及相关问题最合适的方案。对于所有程序上的问题,往往都会有多种解决方案,选择一种最合适的方案才是关键。