spring循环依赖三级缓存的作用

前言

1、spring循环依赖是什么?

2、spring创建bean的过程中的循环依赖

3、循环依赖的解决

4、三级缓存的作用

第一级缓存

第二级缓存

第三级缓存

总结



前言

本文是根据学习了腾讯课堂图灵学院的spring循环依赖学习视频,结合自己的理解所写,主要对自己所写的东西做个记录,方便以后回忆,可能存在不正确的地方,如有问题请多多指教。学习视频链接如下:BAT大厂高频面试题 Spring循环依赖底层原理深度剖析【图灵课堂】-学习视频教程-腾讯课堂https://ke.qq.com/course/3067506


1、spring循环依赖是什么?

简单来说就是类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");
    }
}

2、spring创建bean的过程中的循环依赖

spring创建bean的过程大概可以分为四步。

  1.  实例化,如通过构造函数实例化,得到对象
  2. 属性赋值,如通过给配置了@Autowired的属性赋值
  3. 初始化,如调用前置处理器,初始化方法,后置处理器
  4. 销毁

现在以以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,就会陷入死循环。

3、循环依赖的解决

从上面的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<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);


    /**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>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

4、三级缓存的作用

第一级缓存

/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

单例池,保存了所有完整的spring bean,ConcurrentHashMap保证了线程安全。

第二级缓存

/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> 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.
	 * <p>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,不需要再次创建。

而二级缓存、三级缓存,是对解决循环依赖中出现的其他问题补充。

所以说三级缓存并不是解决循环依赖所必须的,应该说是解决循环依赖及相关问题最合适的方案。对于所有程序上的问题,往往都会有多种解决方案,选择一种最合适的方案才是关键。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值