根据之前解析的循环依赖的源码,分析了一级缓存,二级缓存,三级缓存的作用以及如何解决循环依赖的. 然而在多线程的情况下,Spring在创建bean的过程中,可能会读取到不完整的bean. 下面,我们就来研究两点:
1. 为什么会读取到不完整的bean.
2. 如何解决读取到不完整bean的问题.
和本文相关的spring循环依赖的前两篇博文如下:
3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
一. 为什么会读取到不完整的bean.
我们知道,如果spring容器已经加载完了,那么肯定所有bean都是完整的了,但如果,spring没有加载完,在加载的过程中,构建bean就有可能出现不完整bean的情况
如下所示:
首先,有一个线程要去创建A类,调用getBean(A),他会怎么做呢?
第一步: 调用getSingleton()方法,去缓存中取数据,我们发现缓存中啥都没有,肯定返回null.
第二步: 将其放入到正在创建集合中,标记当前bean A正在创建
第三步: 实例化bean
第四步: 将bean放到三级缓存中. 定义一个函数接口,方便后面调用
addSingletonFactory(beanName,() -> getEarlyBeanReference(beanName,mbd,bean));
第四步: 属性赋值. 在属性赋值的时候,返现要加载类B,就在这个时候,另一个线程也进来了,要创建Bean A.
第五步: 线程2 创建bean,也是先去调用getSinglton()从缓存中取,一二级换粗中都没有,但是三级缓存中却是有的. 于是就调用动态代理,去创建bean,很显然这时候创建的bean是不完整的. 然后将其放入到二级缓存中,二级缓存里的bean也是不完整的. 这就导致了后面是用的bean可能都是不完整的. 详细的分析上图
二. 如何解决读取到不完整bean的问题.
其实,之所以出现这样的问题,原因就在于,第一个bean还没有被创建完,第二个bean就开始了. 这是典型的并发问题.
针对这个问题,其实,我们加锁就可以了.
用自己手写的代码为例
第一: 将整个创建过程加一把锁
/** * 获取bean,根据beanName获取 */ public static Object getBean(String beanName) throws Exception { // 增加一个出口. 判断实体类是否已经被加载过了 Object singleton = getSingleton(beanName); if (singleton != null) { return singleton; } Object instanceBean; synchronized (singletonObjects) { 标记bean正在创建 if (!singletonsCurrectlyInCreation.contains(beanName)) { singletonsCurrectlyInCreation.add(beanName); } * * 第一步: 实例化 * 我们这里是模拟,采用反射的方式进行实例化. 调用的也是最简单的无参构造函数 */ RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName); Class<?> beanClass = beanDefinition.getBeanClass(); 调用无参的构造函数进行实例化 instanceBean = beanClass.newInstance(); * * 第二步: 放入到三级缓存 * 每一次createBean都会将其放入到三级缓存中. getObject是一个钩子方法. 在这里不会被调用. * 什么时候被调用呢? * 在getSingleton()从三级缓存中取数据,调用创建动态代理的时候 singletonFactories.put(beanName,new ObjectFactory() { @Override public Object getObject() throws BeansException { return new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.(beanName),beanName); } }); earlySingletonObjects.put(beanName,instanceBean); * * 第三步: 属性赋值 * instanceA这类类里面有一个属性,InstanceB. 所以,先拿到 instanceB,然后在判断属性头上有没有Autowired注解. * 注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式,一种是name,一种是type Field[] declaredFields = beanClass.getDeclaredFields(); for (Field declaredField : declaredFields) { 判断每一个属性是否有@Autowired注解 Autowired annotation = declaredField.getAnnotation(Autowired.class); if (annotation != ) { 设置这个属性是可访问的 declaredField.setAccessible(true); 那么这个时候还要构建这个属性的bean. * 获取属性的名字 * 真实情况,spring这里会判断,是根据名字,还是类型,还是构造函数来获取类. * 我们这里模拟,所以简单一些,直接根据名字获取. String name = declaredField.getName(); * * 这样,在这里我们就拿到了 instanceB 的 bean Object fileObject = getBean(name); 为属性设置类型 declaredField.set(instanceBean,fileObject); } } * * 第四步: 初始化 * 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了 */ * * 第五步: 放入到一级缓存 * * 在这里二级缓存存的是动态代理,那么一级缓存肯定也要存动态代理的实例. * 从二级缓存中取出实例,放入到一级缓存中 */ if (earlySingletonObjects.containsKey(beanName)) { instanceBean = earlySingletonObjects.(beanName); } singletonObjects.put(beanName,instanceBean); 删除二级缓存 删除三级缓存 } instanceBean; }
然后在从缓存取数据的getSingleton()上也加一把锁
private Object getSingleton(String beanName) { 先去一级缓存里拿, Object bean = singletonObjects.(beanName); 一级缓存中没有,但是正在创建的bean标识中有,说明是循环依赖 if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) { synchronized (singletonObjects) { bean = earlySingletonObjects.(beanName); 如果二级缓存中没有,就从三级缓存中拿 从三级缓存中取 ObjectFactory objectFactory = singletonFactories.(beanName); if (objectFactory != ) { 这里是真正创建动态代理的地方. bean = objectFactory.getObject(); 然后将其放入到二级缓存中. 因为如果有多次依赖,就去二级缓存中判断. 已经有了就不在再次创建了 earlySingletonObjects.put(beanName,bean); } } } } bean; }
加了两把锁.
这样,在分析一下
如上图,线程B执行到getSingleton()的时候,从一级缓存中取没有,到二级缓存的时候就加锁了,他要等待直到线程A完成执行完才能进入. 这样就避免出现不完整bean的情况.
三. 源码解决
在创建实例bean的时候,加了一把锁,锁是一级缓存.
1 public Object getSingleton(String beanName,ObjectFactory<?> singletonFactory) { 2 Assert.notNull(beanName,"Bean name must not be null"); 3 synchronized (this.singletonObjects) { 4 第一步: 从一级缓存中获取单例对象 5 Object singletonObject = this.singletonObjects.(beanName); 6 if (singletonObject == ) { 7 if (this.singletonsCurrentlyInDestruction) { 8 throw BeanCreationNotAllowedException(beanName,9 Singleton bean creation not allowed while singletons of this factory are in destruction " + 10 (Do not request a bean from a beanfactory in a destroy method implementation!)); 11 } 12 (logger.isDebugEnabled()) { 13 logger.debug(Creating shared instance of singleton bean '" + beanName + '1415 第二步: 将bean添加到singletonsCurrentlyInCreation中,表示bean正在创建 16 beforeSingletonCreation(beanName); 17 boolean newSingleton = false; 18 boolean recordSuppressedExceptions = (this.suppressedExceptions == 19 (recordSuppressedExceptions) { 20 this.suppressedExceptions = new LinkedHashSet<>(); 2122 try { 23 第三步: 这里调用getObject()钩子方法,就会回调匿名函数,调用singletonFactory的createBean() 24 singletonObject = singletonFactory.getObject(); 25 newSingleton = 2627 catch (IllegalStateException ex) { 28 Has the singleton object implicitly appeared in the meantime -> 29 if yes,proceed with it since the exception indicates that state. 30 singletonObject = (beanName); 31 ) { 32 throw ex; 33 } 3435 (BeanCreationException ex) { 36 37 for (Exception suppressedException : .suppressedExceptions) { 38 ex.addRelatedCause(suppressedException); 39 } 4041 4243 finally44 45 4647 afterSingletonCreation(beanName); 4849 (newSingleton) { 50 addSingleton(beanName,singletonObject); 5152 } 53 singletonObject; 54 } 55 }
再从缓存中取数据的时候,也加了一把锁,和我们的demo逻辑是一样的. 锁也是一级缓存.
protected Object getSingleton(String beanName,boolean allowEarlyReference) { 从一级缓存中获取bean实例对象 Object singletonObject = * * 如果在第一级的缓存中没有获取到对象,并且singletonsCurrentlyIncreation为true,也就是这个类正在创建. * 标明当前是一个循环依赖. * * 这里有处理循环依赖的问题.-- 我们使用三级缓存解决循环依赖 */ isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { * * 从二级缓存中拿bean,二级缓存中的对象是一个早期对象 * 什么是早期对象?就是bean刚刚调用了构造方法,还没有给bean的属性进行赋值,和初始化,这就是早期对象 singletonObject = this.earlySingletonObjects. allowEarlyReference) { * * 从三级缓存拿bean,singletonFactories是用来解决循环依赖的关键所在. * 在ios后期的过程中,当bean调用了构造方法的时候,把早期对象包装成一个ObjectFactory对象,暴露在三级缓存中 ObjectFactory<?> singletonFactory = this.singletonFactories.if (singletonFactory != * * 在这里通过暴露的ObjectFactory包装对象. 通过调用他的getObject()方法来获取对象 * 在这个环节中会调用getEarlyBeanReference()来进行后置处理 singletonObject = singletonFactory.getObject(); 把早期对象放置在二级缓存中 .earlySingletonObjects.put(beanName,singletonObject); 删除三级缓存 .singletonFactories.remove(beanName); } } } singletonObject; }