曹工說Spring Boot源碼(29)-- Spring 解決循環依賴爲何使用三級緩存,而不是二級緩存

寫在前面的話

相關背景及資源:html

曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java

曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git

曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下spring

曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?express

曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanjson

曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的緩存

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)app

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)spring-boot

曹工說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
     * 一、先查bean容器,查到則返回
     * 二、生成bean,放進容器(此時,依賴還沒注入,主要是解決循環依賴問題)
     * 三、注入依賴
     *
     * @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的代碼,只從第1、第二級緩存獲取,不從第三級獲取

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但願咱們,在這幾處,要返回一樣的對象,即:既然你這幾處都要返回代理對象,那就不能返回不同的代理對象。

源碼

文章用到的aop循環依賴的demo,本身寫一個也能夠,很簡單:

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

不錯的參考資料

http://www.javashuo.com/article/p-smkruniq-nt.html

總結

若是有問題,歡迎指出;歡迎加羣討論;有幫助的話,請點個贊吧,謝謝

相關文章
相關標籤/搜索