写在前面的话

相关背景及资源:

曹工说Spring Boot源码(1)– Bean Definition到底是什么,附spring思维导图分享

曹工说Spring Boot源码(2)– Bean Definition到底是什么,咱们对着接口,逐个方法讲解

曹工说Spring Boot源码(3)– 手动注册Bean Definition不比游戏好玩吗,我们来试一下

曹工说Spring Boot源码(4)– 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

曹工说Spring Boot源码(5)– 怎么从properties文件读取bean

曹工说Spring Boot源码(6)– Spring怎么从xml文件里解析bean的

曹工说Spring Boot源码(7)– Spring解析xml文件,到底从中得到了什么(上)

曹工说Spring Boot源码(8)– Spring解析xml文件,到底从中得到了什么(util命名空间)

曹工说Spring Boot源码(9)– Spring解析xml文件,到底从中得到了什么(context命名空间上)

曹工说Spring Boot源码(10)– Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

曹工说Spring Boot源码(11)– context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)

曹工说Spring Boot源码(12)– Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

曹工说Spring Boot源码(13)– AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

曹工说Spring Boot源码(14)– AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成

曹工说Spring Boot源码(15)– Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

曹工说Spring Boot源码(16)– Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

曹工说Spring Boot源码(17)– Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

曹工说Spring Boot源码(18)– Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)

曹工说Spring Boot源码(19)– Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)

曹工说Spring Boot源码(20)– 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操作日志

曹工说Spring Boot源码(21)– 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了

曹工说Spring Boot源码(22)– 你说我Spring Aop依赖AspectJ,我依赖它什么了

曹工说Spring Boot源码(23)– ASM又立功了,Spring原来是这么递归获取注解的元注解的

曹工说Spring Boot源码(24)– Spring注解扫描的瑞士军刀,asm技术实战(上)

曹工说Spring Boot源码(25)– Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

曹工说Spring Boot源码(26)– 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎

曹工说Spring Boot源码(27)– Spring的component-scan,光是include-filter属性的各种配置方式,就够玩半天了

曹工说Spring Boot源码(28)– Spring的component-scan机制,让你自己来进行简单实现,怎么办

工程代码地址 思维导图地址

工程结构图:

什么是三级缓存

在获取单例bean的时候,会进入以下方法:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
    
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
                // 1
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
                                // 2
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
                                        // 3
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
                                                // 4
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

这里面涉及到了该类中的三个field。

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

	/** 2级缓存 Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

	/** 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

接着说前面的代码。

  • 1处,在最上层的缓存singletonObjects中,获取单例bean,这里面拿到的bean,直接可以使用;如果没取到,则进入2处

  • 2处,在2级缓存earlySingletonObjects中,查找bean;

  • 3处,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象(工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean),去获取包装后的bean,或者说,代理后的bean。

    什么是已经创建好的,但没有注入属性的bean?

    比如一个bean,有10个字段,你new了之后,对象已经有了,内存空间已经开辟了,堆里已经分配了该对象的空间了,只是此时的10个field还是null。

ioc容器,普通循环依赖,一级缓存够用吗

说实话,如果简单写写的话,一级缓存都没问题。给大家看一个我以前写的渣渣ioc容器:

曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器

@Data
public class BeanDefinitionRegistry {
    /**
     * map:存储 bean的class-》bean实例
     */
    private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();
    
    /**
     * 根据bean 定义获取bean
     * 1、先查bean容器,查到则返回
     * 2、生成bean,放进容器(此时,依赖还没注入,主要是解决循环依赖问题)
     * 3、注入依赖
     *
     * @param beanDefiniton
     * @return
     */
    private Object getBean(MyBeanDefiniton beanDefiniton) {
        Class<?> beanClazz = beanDefiniton.getBeanClazz();
        Object bean = beanMapByClass.get(beanClazz);
        if (bean != null) {
            return bean;
        }
		// 0
        bean = generateBeanInstance(beanClazz);


        // 1 先行暴露,解决循环依赖问题
        beanMapByClass.put(beanClazz, bean);
        beanMapByName.put(beanDefiniton.getBeanName(), bean);

        // 2 查找依赖
        List<Field> dependencysByField = beanDefiniton.getDependencysByField();
        if (dependencysByField == null) {
            return bean;
        }
		
        // 3
        for (Field field : dependencysByField) {
            try {
                autowireField(beanClazz, bean, field);
            } catch (Exception e) {
                throw new RuntimeException(beanClazz.getName() + " 创建失败",e);
            }
        }

        return bean;
    }
}

大家看上面的代码,我只定义了一个field,就是一个map,存放bean的class-》bean。

    /**
     * map:存储 bean的class-》bean实例
     */
    private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();
  • 0处,生成bean,直接就是new
  • 1处,先把这个不完整的bean,放进map
  • 2处,获取需要注入的属性集合
  • 3处,进行自动注入,就是根据field的Class,去map里查找对应的bean,设置到field里。

上面这个代码,有啥问题没?spring为啥整整三级?

ioc,一级缓存有什么问题

一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。

如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。

所以,我们就要加一个map,这个map,用来存放那种不完整的bean。这里,还是拿spring举例。我们可以只用下面这两层:

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

	/** 2级缓存 Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

因为spring代码里是三级缓存,所以我们对源码做一点修改。

修改spring源码,只使用二级缓存

修改创建bean的代码,不放入第三级缓存,只放入第二级缓存

创建了bean之后,属性注入之前,将创建出来的不完整bean,放到earlySingletonObjects

这个代码,在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,我这边只有4.0版本的spring源码工程,不过这套逻辑,算是spring核心逻辑,和5.x版本差别不大。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
            // 1
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
		...
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
            // 2
			earlySingletonObjects.put(beanName,bean);
			registeredSingletonObjects.add(beanName);
			// 3
//			addSingletonFactory(beanName, new ObjectFactory() {
//				public Object getObject() throws BeansException {
//					return getEarlyBeanReference(beanName, mbd, bean);
//				}
//			});
		}
  • 1处,就是创建对象,就是new
  • 2处,这是我加的代码,放入二级缓存
  • 3处,本来这就是增加三级缓存的位置,被我注释了。现在,就不会往三级缓存放东西了

修改获取bean的代码,只从第一、第二级缓存获取,不从第三级获取

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

之前的代码是文章开头那样的,我这里修改为:

	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);
				return singletonObject;
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);

这样,就是只用两级缓存了。

两级缓存,有啥问题?

ioc循环依赖,一点问题都没有,完全够用了。

我这边一个简单的例子,


public class Chick{
    private Egg egg;

    public Egg getEgg() {
        return egg;
    }

    public void setEgg(Egg egg) {
        this.egg = egg;
    }
}

public class Egg {
    private Chick chick;

    public Chick getChick() {
        return chick;
    }

    public void setChick(Chick chick) {
        this.chick = chick;
    }
    <bean id="chick" class="foo.Chick" lazy-init="true">
        <property name="egg" ref="egg"/>
    </bean>
    <bean id="egg" class="foo.Egg" lazy-init="true">
        <property name="chick" ref="chick"/>
    </bean>
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                "context-namespace-test-aop.xml");

        Egg egg = (Egg) ctx.getBean(Egg.class);

结论:

所以,一级缓存都能解决的问题,二级当然更没问题。

但是,如果我这里给上面的Egg类,加个切面(aop的逻辑,意思就是最终会生成Egg的一个动态代理对象),那还有问题没?

    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(public * foo.Egg.*(..))"/>
        <aop:aspect id="myAspect" ref="performenceAspect">
            <aop:after method="afterIncubate" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>

注意这里的切点:

execution(public * foo.Egg.*(..))

就是切Egg类的方法。

加了这个逻辑后,我们继续运行,在 Egg egg = (Egg) ctx.getBean(Egg.class);行,会抛出如下异常:

我涂掉了一部分,因为那是官方对这个异常的推论,因为我们改了代码,所以推论不准确,因此干脆隐去。

这个异常是说:

兄弟啊,bean egg已经被注入到了其他bean:chick中。(因为我们循环依赖了),但是,注入到chick中的,是Egg类型。但是,我们这里最后对egg这个bean,进行了后置处理,生成了代理对象。那其他bean里,用原始的bean,是不是不太对啊?

所以,spring给我们抛错了。

怎么理解呢? 以io流举例,我们一开始都是用的原始字节流,然后给别人用的也是字节流,但是,最后,我感觉不方便,我自己悄悄弄了个缓存字符流(类比代理对象),我是方便了,但是,别人用的,还是原始的字节流啊。

你bean不是单例吗?不能这么玩吧?

所以,这就是二级缓存,不能解决的问题。

什么问题?aop情形下,注入到其他bean的,不是最终的代理对象。

三级缓存,怎么解决这个问题

要解决这个问题,必须在其他bean(chick),来查找我们(以上面例子为例,我们是egg)的时候,查找到最终形态的egg,即代理后的egg。

怎么做到这点呢?

加个三级缓存,里面不存具体的bean,里面存一个工厂对象。通过工厂对象,是可以拿到最终形态的代理后的egg。

ok,我们将前面修改的代码还原:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
            // 1
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
            // 2
//			Map<String, Object> earlySingletonObjects = this.getEarlySingletonObjects();
//			earlySingletonObjects.put(beanName,bean);
//
//			Set<String> registeredSingletonObjects = this.getRegisteredSingletonObjects();
//			registeredSingletonObjects.add(beanName);
			
            // 3
			addSingletonFactory(beanName, new ObjectFactory() {
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});
		}
  • 1处,创建bean,单纯new,不注入

  • 2处,revert我们的代码

  • 3处,这里new了一个ObjectFactory,然后会存入到如下的第三级缓存。

    	/** 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    

    注意,new一个匿名内部类(假设这个匿名类叫AA)的对象,其中用到的外部类的变量,都会在AA中隐式生成对应的field。

    大家看上图,里面的3个字段,和下面代码1处中的,几个字段,是一一对应的。

    			addSingletonFactory(beanName, new ObjectFactory() {
    				public Object getObject() throws BeansException {
                        // 1
    					return getEarlyBeanReference(beanName, mbd, bean);
    				}
    			});
    

ok,现在,egg已经把自己存进去了,存在了第三级缓存,1级和2级都没有,那后续chick在使用getSingleton查找egg的时候,就会进入下面的逻辑了(就是文章开头的那段代码,下面已经把我们的修改还原了):

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);
//				return singletonObject;
//			}
//		}
//		return (singletonObject != NULL_OBJECT ? singletonObject : null);

		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) {
                        // 1
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}

上面就会进入1处,调用singletonFactory.getObject();

而前面我们知道,这个factory的逻辑是:

			addSingletonFactory(beanName, new ObjectFactory() {
				public Object getObject() throws BeansException {
                    // 1
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});

1处就是这个工厂方法的逻辑,这里面,简单说,就会去调用各个beanPostProcessor的getEarlyBeanReference方法。

其中,主要就是aop的主力beanPostProcessor,AbstractAutoProxyCreator#getEarlyBeanReference

其实现如下:

	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.add(cacheKey);
        // 1
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

这里的1处,就会去对egg这个bean,创建代理,此时,返回的对象,就是个代理对象了,那,注入到chick的,自然也是代理后的egg了。

关于SmartInstantiationAwareBeanPostProcessor

我们上面说的那个getEarlyBeanReference就在这个接口中。

这个接口继承了BeanPostProcessor

而创建代理对象,目前就是在如下两个方法中去创建:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;    
}

这两个方法,都是在实例化之后,创建代理。那我们前面创建代理,是在依赖解析过程中:

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
    ...
	Object getEarlyBeanReference(Object bean, String beanName) throws BeansException;
}

所以,spring希望我们,在这几处,要返回同样的对象,即:既然你这几处都要返回代理对象,那就不能返回不一样的代理对象。

那我们再看看,到底,AbstractAutoProxyCreator有没有遵守约定呢,这几个方法里,有没有去返回同样的代理包装对象呢?

getEarlyBeanReference

	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 1
		this.earlyProxyReferences.add(cacheKey);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

1处,往field:

private final Set<Object> earlyProxyReferences =
      Collections.newSetFromMap(new ConcurrentHashMap<Object, Boolean>(16));

里,加了个cachekey,这个cachekey,主要也就是如下的字符串,用来唯一标识而已。

	protected Object getCacheKey(Class<?> beanClass, String beanName) {
		return beanClass.getName() + "_" + beanName;
	}

我们可以看看这个field在哪里被用到了。

也就两处,一处就是当前位置;另外一处,下面讲。

这里,主要就是看看到底要不要生成代理对象,要的话,就生成,不要就算了,另外,做了个标记:在earlyProxyReferences加了当前bean的key,表示:当前bean,已经被getEarlyBeanReference方法处理过了。

至于,最终到底有没有生成代理对象,另说。毕竟调用wrapIfNecessary也不是说,一定就满足切面,要生成代理对象。

可能返回的仍然是原始对象。

postProcessBeforeInitialization

public Object postProcessBeforeInitialization(Object bean, String beanName) {
   return bean;
}

这一处,没做处理。

postProcessAfterInitialization

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // 1
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

这里,1处这个判断哈,就用到了前面我们说的那个field。那个field,只在两处用,一处就是调用getEarlyBeanReference,会往里面把当前bean的key放进去;另外一处,就是这里。

这里判断,如果field里不包含当前bean,就去调用wrapIfNecessary;如果包含(意味着,getEarlyBeanReference处理过了),就不调用了。

这里,说到底,就是保证了,wrapIfNecessary只被调用一次。

看吧,wrapIfNecessary也就这两处被调用了。

所以,我们可以得出结论,在aop这个beanPostProcessor中,有多处机会可以返回一个proxy对象,但是,最终,只要在其中一处处理了,其他处,根本不再继续处理。

另外,还有一点很重要,在这个aop beanPostProcessor中,传入了原始的bean,我们会去判断,是否要给它创建代理,如果要,就创建;如果不要则:

返回原始对象

整个流程串起来

上面这个后置处理器看明白了,接下来,再看看创建bean的核心流程:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
		// 1 
		BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
		final Object bean = instanceWrapper.getWrappedInstance();
		
		if (earlySingletonExposure) {
            // 2
			addSingletonFactory(beanName, new ObjectFactory() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});
		}

		// 3 
		Object exposedObject = bean;
    	// 4
        populateBean(beanName, mbd, instanceWrapper);
		
    	// 5
        if (exposedObject != null) {
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }

		if (earlySingletonExposure) {
            // 6
			Object earlySingletonReference = getSingleton(beanName, false);
            
			if (earlySingletonReference != null) {
                // 7
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    // 8
                    ...
				}
			}
		}

		return exposedObject;
	}

上面流程中,做了部分删减。但基本创建一个bean,就这几步了。

  • 1处,创建bean对象,此时,属性什么的全是null,可以理解为,只是new了,field还没设置

  • 2处,添加到第三级缓存;加进去的,只是个factory,只有循环依赖的时候,才会发挥作用

  • 3处,把原始bean,存到exposedObject

  • 4处,填充属性;循环依赖情况下,A/B循环依赖。假设当前为A,那么此时填充A的属性的时候,会去:

    new B;

    填充B的field,发现field里有一个是A类型,然后就去getBean(“A”),然后走到第三级缓存,拿到了A的ObjectFactory,然后调用ObjectFactory,然后调用AOP的后置处理器类:getEarlyBeanReference,拿到代理后的bean(假设此处切面满足,要创建代理);

    经过上面的步骤后,B里面,field已经填充ok,其中,且填充的field是代理后的A,这里命名为proxy A。

    B 继续其他的后续处理。

    B处理完成后,被填充到当前的origin A(原始A)的field中

  • 5处,对A进行后置处理,此时调用aop后置处理器的,postProcessAfterInitialization;前面我们说了,此时不会再去调用wrapIfNecessary,所以这里直接返回原始A,即 origin A

  • 6处,去缓存里获取A,拿到的A,是proxy A

  • 7处,我们梳理下:

    exposedObject:origin A

    bean:原始A

    earlySingletonReference: proxy A

    此时,下面这个条件是满足的,所以,exposedObject,最终被替换为proxy A:

    if (exposedObject == bean) {
        exposedObject = earlySingletonReference;
    }
    

源码

文章用到的aop循环依赖的demo,自己写一个也可以,很简单:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-aop-xml-demo-cycle-reference

更新于2020-07-01:回答评论区问题

问题描述:只使用第一、第二级缓存,是否可行

这两个问题,本质是一个问题,就是,不用第三级的ObjectFactory行不行,代码直接调用getEarlyBeanReference,拿到bean A的早期引用后,放到第二级缓存,后续bean B去解析依赖时,,注入的不就是最终形态的bean A了吗。

我回答的时候,只是思考了一下,并没有实际测试。下面我们修改下源码,测试一下。

修改源码

获取单例的地方,修改为只使用第一、第二级缓存

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);
				return singletonObject;
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}

创建bean的地方,修改代码,获取earlyBeanReference后,手动放入第二级缓存

如下是原始代码:

AbstractAutowireCapableBeanFactory#doCreateBean
	// 原始代码长这样
    addSingletonFactory(beanName, new ObjectFactory() {

        @Override
        public Object getObject() throws BeansException {
            Object earlyBeanReference = getEarlyBeanReference(beanName, mbd, bean);

            if (earlyBeanReference != bean) {
                log.info("{} has been wrapped to {}", bean,earlyBeanReference);
            }

            return earlyBeanReference;
        }
    });    

修改后,长这样:

/**
 * 只使用第一、第二级缓存,即,只使用:
 * singletonObjects
 * earlySingletonObjects
 * 不使用第三级:
 * singletonFactories
 */
// 1
Object earlyBeanReference = getEarlyBeanReference(beanName, mbd, bean);
// 2
addEarlyReference(beanName, earlyBeanReference);

1处,主要是,获取了早期引用,然后2处,调用我们自己写的一个方法:

DefaultSingletonBeanRegistry#addEarlyReference
/**
 * 修改版本,直接添加早期引用
 * {@link SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(Object, String)}
 * 拿到早期引用,然后添加到earlySingletonObjects
 * 不使用第三级缓存singletonFactories
 * @param beanName
 * @param earlyReference  调用getEarlyBeanReference(java.lang.Object, java.lang.String)
 */
protected void addEarlyReference(String beanName, Object earlyReference) {
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         // 1
         this.earlySingletonObjects.put(beanName,earlyReference);
         this.registeredSingletons.add(beanName);
      }
   }
}

1处代码,把早期引用,存到了二级缓存。

测试该场景下有什么问题

  1. 首先,我们继续用前面的例子,我们要getBean(egg),egg这个bean,依赖了chick,chick又依赖egg。egg最终要生成代理对象。

  2. 首先,创建egg,获取其早期引用,放到二级缓存。

    这里注意,egg虽然生成了代理对象,但是,其属性,chick是null。

  3. 接下来,开始egg解析依赖的时候,发现依赖了chick,所以,会去创建chick,并创建chick的早期引用

    到此为止,早期引用中,已经存放了两个对象,egg(代理对象,但chick属性为null),chick。

  4. 填充chick的field:egg

此时,chick已经好了。唯一的问题是,egg还没好,egg里面的chick属性,还是null。

  1. egg此时的field依赖,chick已经解决了,此时的egg,长这样:

  2. 此时,egg的属性已经填充了,是不是该去生成代理了

	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // 1
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

不过这里的1处,earlyProxyReferences已经包含了egg这个bean了,所以不会再去生成代理。

  1. 使用不完整的早期引用,替换了populateDependency完成的egg,出大事了

    if (earlySingletonExposure) {
    			Object earlySingletonReference = getSingleton(beanName, false);
    			if (earlySingletonReference != null) {
                    // 1
    				if (exposedObject == bean) {
                        // 2
    					exposedObject = earlySingletonReference;
    				}
                }
    

    此时,1处是满足的,见上图。所以,进入2处,2处的earlySingletonReference,就是我们从二级缓存拿到的早期引用,这个早期引用,一切都好,唯一的问题是,这里的field:chick是null

    所以,问题,就是这么个问题,只用二级缓存,此时的chick是null。

更新于2020-07-04:手动调用getEarlyReferencde,放到二级缓存,真的有问题吗

这次的评论区问题,说实话,我非常感谢,因为,借此问题,我发现,我上面讲的:

更新于2020-07-01的那部分,结论是错误的。

这个问题是什么意思呢?就是这位同学认为:

更新于2020-07-01那部分的试验,最终我的结论是,最终这个Egg对象,是一个代理对象,但是其中的Chick field是null,所以,有问题。

但是,正常情况下,生成的代理对象,其中的field,本来就是null。

我举个例子,是我们实际中的业务代码:

@Service
public class SeatInformationServiceImpl extends ServiceImpl<SeatInformationMapper, SeatInformation> implements ISeatInformationService {
    @Autowired
    private SeatInformationMapper seatInformationMapper;
    @Autowired
    private ICenterService centerService;

    @Autowired
    private RestTemplate restTemplate;
    
    // 1
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(String token,Long seatId) throws BusinessException {
        ...
    }

这个service,就是我从业务代码里copy的,其中,1处,注解了Transactional注解,而事务一般就是基于aop实现的,所以,最终这个service,肯定是会生成代理对象的。

我们看看这个生成的代理对象,长什么样子?

而代理对象,要获取真正的target时,是可以拿到的,如下所示。

ok,所以,我2020-07-01的试验中,过程是没问题的,但结论有问题。

  • 错误结论:手动调用getEarlyReference放入二级缓存,去掉三级缓存,这样问题
  • 正确结论:手动调用getEarlyReference放入二级缓存,去掉三级缓存,这样没有问题

而且,关键是,我在这种试验场景下,最终去执行如下代码,切面也是生效了的:

public static void main(String[] args) {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
            "context-namespace-test-aop.xml");

    Egg egg = (Egg) ctx.getBean(Egg.class);
    egg.incubate();
}

开始孵化
09:45:41.754 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory – Returning cached instance of singleton bean \’performenceAspect\’
孵化完成 ——–这部分是切面打印的

既然先行手动调用getEarlyBeanReference这种方案可以解决问题,为什么还要弄三级缓存

问题就在于,我们每次都去调用getEarlyReference,是可以解决问题,没错。但是,这一步,很多时候都是没有必要的。

在没有循环依赖的时候,这个方法,是从来不会被调用的;也就是说,我们为了解决系统中那百分之1可能出现的循环依赖问题,而让百分之99的bean,创建时,都去走上这么一圈。

效率说不过去吧?

ok,大家要明白,这个方法,SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference,其存在的意义,就是为了解决aop场景下的循环依赖。

没有这个场景,就不需要这个方案。

大家可以仔细思考下,上面的红圈这里,这个条件,什么时候才会是true?

不错的参考资料

https://blog.csdn.net/f641385712/article/details/92801300

总结

如果有问题,欢迎指出;欢迎加群讨论;有帮助的话,请点个赞吧,谢谢

版权声明:本文为grey-wolf原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/grey-wolf/p/13034371.html