spring源碼系列(五)——番外篇回答網友的問題

回答一下@椰大大�的問題;他在第四篇的文章評論裏面留言了;可是他的問題比較複雜;爲了把問題講清楚就挪到這裏來回答吧;html

問題原文:

有個問題在網上找半天,問了一堆人也不會,只能留言請教你了。。。 爲什麼@Autowired能夠注入ApplicationContext, 通常來講,咱們能夠經過實現ApplicationContextAware接口來獲取ApplicationContext的引用。可是根據官方文檔,發現也能夠經過 @Autowired來注入ApplicationContext,這是爲何呢?java

問題1——去了哪裏?

若是你提問我確定回答;因此確定還在CSDN。有空確定奮筆疾書寫完spring源碼分析的博客ios

爲什麼@Autowired能夠注人 ApplicationContext對象?

這個問題比較複雜;獲得ApplicationContext對象的方法多了去了,不止這兩種,我就不一一例舉了;好比extends WebApplicationObjectSupport也能夠獲取ApplicationContext對象; 並且可能你有兩重意思 一、有了ApplicationContextAware已經獲得ApplicationContext爲何還須要@Autowried? 二、 爲什麼@Autowired能夠注人 ApplicationContext對象,原理是什麼? 我都回答一下吧——正文開始程序員

獲取AppliationContext的主流方式

爲了適合更對spring還不那麼熟悉的讀者我先把主流的兩種獲取ApplicationContext對象的方式列出來; 第一種,在一個bean(注意必定得是bean,被spring容器管理的對象)當中經過@Autowired來注入ApplicationContext對象;代碼以下:spring

@Component
public class X {
	@Autowired
	Y y;

	@Autowired
	ApplicationContext applicationContext;

	public X(){
		System.out.println("x 的構造方法");
	}
}

複製代碼

第二種,經過實現ApplicationContextAware接口來獲取AppliationContext對象;api

@Comonpent
public class Util implements ApplicationContextAware{
	private static ApplicationContext applicationContext = null;
	public  void setApplicationContext(ApplicationContext applicationContext){
		Util.applicationContext =applicationContext;
	}

	//能夠對getBean封裝
	public static Object getBean(String name){
		sout("可能你須要幹一點其餘事吧");
    	return getApplicationContext().getBean(name);
  }
	//也能夠提供一個靜態方法返回applicationContext
	代碼省略了
}

複製代碼

兩種獲取ApplicationContext對象的方式有什麼區別呢?

@Autowried和ApplicationContextAware都能實現獲得一個ApplicationContext(再次說明,其實不止這兩種方式;可是其餘的方式能夠忽略,所有歸類爲接口方式);那麼這兩種方式有什麼區別呢?我認爲沒什麼區別,無非是一個耦合是@Autowried這個註解;另外一個耦合的是一個接口;如今的spring版本已經5.x了;若是大家公司使用的spring不支持註解那麼就使用接口吧;緩存

爲何須要注入這個ApplicationContext對象呢?

我列舉一下官網上說的經典場景吧;bash

假設類A(單例的)須要注入一個類B(原型的);若是咱們直接寫會有問題;app

好比你在A類當中的m()方法中返回b,那麼不管你調用多少次a.m();返回的都是同一個b對象;就違背b的原型規則,應該在m方法中每次都返回一個新的b;因此某些場景下b不能直接注入;ide

錯誤的代碼:

@Component
public class A{

	//注意B是原型的  scope=prototype
	@Autowried;
	B b;
	public B m(){

		//直接返回注入進來的b;確定有問題
		//返回的永遠是A實例化的時候注入的那個bean
		//違背的B設計成原型的初衷
		return b;
	}
}

複製代碼

正確的寫法

@Component
public class A{
	@Autowired
	ApplicationContext applicationContext;
	public B m(){
		//每次調用m都是經過spring容器去獲取b
		//若是b是原型,每次拿到的都是原型b
		B b= applicationContext.getBean("b");
		return b;
	}
}

複製代碼

固然這個不是我胡說,這是官網的上面的經典例子 官網參考:docs.spring.io/spring/docs…

In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the dependency by defining one bean as a property of the other. A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container creates the singleton bean A only once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.

讀者能夠本身打開這一章節;能夠自行翻譯一下;上面是筆者蹩腳的英文水平的理解;說不定你有更加深入的理解也說不定;

再說一個你們常見的場景

好比A這個類不是被spring管理的對象;可是須要獲得一個spring管理的B;這個時候固然不能用傳統的@Autowired來注入B,由於A都不是bean,你在A裏面寫spring註解都不可能生效;只能獲取ApplicationContext對象,而後調用getBean方法獲得b;

固然可能還有更多場景須要用到這個對象就再也不囉嗦了;

爲何@Autowired可以注入ApplicationContext對象?

好比上文中的X裏面注入了一個Y;注入了一個ApplicationContext對象;爲何這個ApplicatonContext能夠注入成功?這個問題其實很經典;可能有人會想固然的認爲確定能夠注入啊;就像注入Y同樣簡單;假設你對spring有一點點了解你便知道Y之因此可以被注入是由於Y自己就存在spring容器當中;換句話說Y在單例池當中;那麼ApplicationContext對象究竟在不在spring容器當中?或者在不在單例池當中呢?

要搞清這個問題的話只有一個辦法 Debug It;

首先咱們假設ApplicationContext 這個對象也存在spring容器當中;其實若是你讀過前面的博客就知道一個bean若是存在spring容器當中,大部分(有的是直接把對象put到單例池,故而沒有BeanDefinition)的狀況下會有一個與之對應的BeanDefinition對象;也存在容器當中;那麼咱們看看這個ApplicationContext的BeanDefinition對象有沒有呢?

AnnotationConfigApplicationContext ac
			= new AnnotationConfigApplicationContext(App.class);

	String[] beanDefinitionNames = ac.getBeanDefinitionNames();
	//打印spring容器當中全部bean的bd
	for (String beanDefinitionName : beanDefinitionNames) {
		System.out.println(beanDefinitionName);
	}
}

複製代碼

執行結果

運行結果是沒有與之對應的beanDefinition對象存在容器;說穿了ApplicationContext這個對象不在容器當中;須要注意的是我這裏說的容器這個概念特別龐大,這裏不展開講;其實按照個人理解ApplicationContext這個對象是在容器當中的;可是因爲以前文章沒有去系統的聊過什麼叫作容器,那麼你們就先按照本身的理解去看待容器;若是按照通常的理解,看到這裏能夠認爲ApplicationContext不在容器當中吧(再次說明我理解的是在容器當中的);之後再來解釋容器這個概念;

若是上面的結果不足以說服你,那麼筆者再列出一個證據; 試想一下咱們注入的這個ApplicationContext對象確定單例的;若是每次注入的ApplicationContext都是一個新的那確定不合理;ApplicationContext若是是單例的講道理他會存在單例池當中;因此咱們能夠看看單例池是否存在這個對象;

能夠看到連Y都被實例化而且存在單例池裏面了(這也是Y之因此可以用@Autowried注入進來的緣由),可是沒有看到ApplicationContext對象;那麼能夠確定ApplicationContext對象不在spring容器當中(再再再再次說明,其實筆者認爲他是存在容器當中的;可能我對容器的理解可能更深一點吧);既然他不在容器當中,也就是和Y不同;怎麼注入進來的呢?

若是你係統的閱讀過spring源碼就會知道完成@Autowried這個註解功能的類是AutowiredAnnotationBeanPostProcessor這個後置處理器;說穿了對@Autowried這個註解的解析就是這個後置處理器;咱們能夠看看他是如何完成@Autowried解析、注入屬性的;找到這個後置處理器當中完成屬性注入的方法——debug it;

postProcessProperties方法即是處理屬性注入的方法;

圖上能夠看到X已經實例化成對象了(還不是bean),可是裏面的屬性都是null,由於他纔剛剛開始來填充屬性;調用metadata.inject(bean, beanName, pvs);繼續完成填充屬性——debug it

metadata.inject(bean, beanName, pvs);首先會遍歷當前bean——當中全部須要注入的屬性;也就是有兩個Y 和 ApplicationContext; 因此第一次for循環注入的是y;就是從單例池當中獲取一下Y;若是獲取不到就實例化Y放到單例池而後返回;反射填充屬性Y;完成注入;

在這裏插入圖片描述

固然咱們關注的是ApplicationContext的注入;因此調試第二次循環——element==ApplicationContext的時候

完成ApplicationContext的步驟—— 多圖警告: 一、調用element.inject(target, beanName, pvs);

二、調用beanFactory.resolveDependency方法獲得ApplicationContext對象;主要就是這裏怎麼獲得的?他不在單例池當中爲什麼能夠獲取到?

三、調用doResolveDependency方法獲取ApplicationContext對象;

四、調用findAutowireCandidates獲取ApplicationContext對象

五、關鍵代碼了

貼一下主要代碼吧

Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
	for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
		Class<?> autowiringType = classObjectEntry.getKey();
		if (autowiringType.isAssignableFrom(requiredType)) {
			Object autowiringValue = classObjectEntry.getValue();
			autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
			if (requiredType.isInstance(autowiringValue)) {
				result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
				break;
			}
		}
	}

複製代碼

對這段代碼作一點解釋吧

當代碼執行到findAutowireCandidates的時候,傳了一個特別重要的參數 Class requireType;就是當前注入的屬性的類型——也就是ApplicationContext.class;

而後遍歷了一個map——resolvableDependencies(關於這個map,下文有解釋)

for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {

複製代碼

接着會把傳過來的requireType和遍歷出來的autowiringType——當前類型進行比較

if (autowiringType.isAssignableFrom(requiredType)) {

複製代碼

若是傳過來的requiredType和遍歷出來Class對象相同則中止遍歷,直接把當前遍歷出來的對象返回做爲注入屬性的值,完成屬性注入;

若是你調試代碼能夠看到這個map——resolvableDependencies一共就四個對象;

這個四個對象就包含了一個ApplicationContext,因此在@Autowired 注入ApplicationContext的時候這個for循環會進入,而且直接返回了map當中已經存在好的ApplicationContext對象以便完成屬性的注入;可是若是普通bean的注入,好比X注入Y,這不會進入這個for循環;咱們能夠證實一下;

注入ApplicationContext的時候:

能夠看到for循環已經進入,而且判斷成功 autowiringValue已經有值了,進入if,break而後返回這個autowiringValue,完成屬性注入;

再來看看普通屬性y的注入和這個是否有差異呢?

這個時候就是x注入y,繼續debug會發現整個for循環當中的if是不會進的;也就是至關於這個for循環沒有任何做用;

能夠看到findAutowireCandidates已經執行完了,可是須要注入的屬性y還只是一個class,不是對象;後臺也只打印了x的構造方法沒有打印y的;說穿了到到此爲止y仍是沒有找到;

可是若是須要注入的屬性是ApplicationContext這裏獲得的就不同,由於上面已經說了for循環裏面已經返回了對象; 證實一下吧

那麼如今Y(對象或者bean)是如何得到並返回的?

其實這個不是這裏討論的,我若是可以把spring源碼系列文章寫完會會寫到屬性注入的所有過程和原理的;

這裏先給個基本結果吧

也就是上圖這個代碼這裏把Y的對象或者叫作bean獲取到返回出去完成屬性注入;

說了這麼多總結一下吧:

普通對象的注入若是注入的屬性是單例,那麼spring首先從單例池獲取,若是獲取不到直接實例化這個bean,放到單例池在返回,完成注入;若是是原型的每次注入就直接實例化這個原型bean返回完成注入;

ApplicationContext對象的注入不一樣,若是注入的屬性是ApplicationContext類型,那麼spring會先從resolvableDependencies這個map當中去找,若是找到直接返回一個ApplicationContext對象完成屬性注入;

那麼問題來了,resolvableDependencies這個map的做用是什麼 他其實有一個javadoc

/** Map from dependency type to corresponding autowired value. */
	//緩存一些依賴類型通用自動注入的值
	private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);

複製代碼

以我蹩腳的英文水平理解這個map緩存一些依賴類型通用自動注入的值;也就是@Autowried的時候有一些通用或者經常使用的類型的值都存放這個map裏面;

若是這個javadoc不是很詳細能夠參考另一個;這個map其實spring沒有開發給程序員使用,private的;那麼可想而知他確定提供了api來操做這個map;找到他,看看這個api的說明

Register a special dependency type with corresponding autowired value 註冊一個特殊的依賴類型——通用的注入的值 換句話說好比你有一些通用的對象、可能會被別的bean注入,那麼你能夠調用這個方法把這寫對象放到一個map當中——resolvableDependencies 下面還有更加詳細的說明,意思說spring當中的一些工廠或者上下文對象他們在bean工廠裏面不是定義爲bean,這個時候若是別的bean須要注入,則能夠把他們放到這個map當中;

那麼spring何時把這四個對象放到這個map當中的呢?——spring容器初始化的時候 我先把調用鏈列出來,而後在截幾個圖說明一下: 0、main方法 一、org.springframework.context.support.AbstractApplicationContext#refresh 二、org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 三、org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency

這就是爲何咱們能夠注入ApplicationContext的所有緣由吧;

相關文章
相關標籤/搜索