Spring注解的實(shí)現(xiàn)原理其實(shí)分很多種情況仰泻,我們這里不概括所有的通用的流程荆陆,而是結(jié)合具體的注解來具體分析。
一集侯,@Configuration注解的實(shí)現(xiàn)原理
上圖:
下面文字解讀一下大體流程:
第一步:Spring容器初始化時(shí)被啼,會注冊一個(gè)后置處理器,解析@Configuration注解的后置處理器是ConfigurationClassPostProcessor棠枉。
第二步:Spring容器初始化時(shí)浓体,當(dāng)執(zhí)行refresh()方法時(shí),會調(diào)用第一步注冊的后置處理器ConfigurationClassPostProcessor辈讶。
第三步:ConfigurationClassPostProcessor后置處理器借助ConfigurationClassParser完成配置類解析命浴。
第四步:ConfigurationClassParser配置類解析過程中會完成嵌套注解,迭代解析掃描包下面的class類的@Component注解的解析贱除。
第五步生闲,注冊bean到spring容器。
二月幌,AnnotationConfigApplicationContext注解處理器實(shí)現(xiàn)原理
AnnotationConfigApplicationContext注解處理器是Spring boot提供的一個(gè)比較常用的注解處理器碍讯。我們先看看它的工作原理。
第一步:初始化讀取器AnnotationBeanDefinitionReader
第二步:初始化掃描器AnnotationBeanDefinitionScanner
第三步:調(diào)用父類GenericApplicationContext的無參構(gòu)造方法初始化一個(gè)BeanFactory扯躺。這里初始化使用的是DefaultListableBeanFactory捉兴。
第四步:注冊Bean配置類
第五步:刷新上下文
這里注冊Bean配置類是關(guān)鍵蝎困,也是最復(fù)雜的一塊,我們來單獨(dú)過一遍倍啥。
三禾乘,AnnotationConfigApplicationContext注冊Bean詳細(xì)流程
AnnotationConfigApplicationContext注冊Bean的具體實(shí)現(xiàn)是下面這行代碼:
this. reader. register(annotatedGlasses);
這里的reader就是我們上面提到的讀取器AnnotatedBeanDefinitionReader,register(annotatedGlasses)方法的具體實(shí)現(xiàn)就在這個(gè)讀取器中逗栽。
接下來我們分步驟來看一下register方法的具體實(shí)現(xiàn)細(xì)節(jié)盖袭。
第一步:解析Bean注解信息。AnnotationConfigApplicationContext使用AnnotationGenericBeanDefinition來解析Bean的注解信息彼宠。解析完成以后得到一個(gè)AnnotationGenericBeanDefinition對象abd鳄虱。
第二步:解析Bean作用域scope及scopeProxyMode。
使用默認(rèn)的Bean作用域處理器ScopeMetaDataResolver解析得到Bean的scope信息凭峡。默認(rèn)情況下拙已,作用域是singleton,scopeProxyMode是NO摧冀。也就是說這個(gè)Bean是單例的倍踪,并且不使用代理模式。如果我們通過Scope注解指定了作用域的話索昂,則使用我們指定的作用域建车。
不同的作用域,在解析注解椒惨,注冊Bean的時(shí)候并沒有區(qū)別缤至。但是在生成Bean的時(shí)候就有區(qū)別了,以singleton為例康谆,會先判斷是否已經(jīng)生成過實(shí)例领斥,如果已經(jīng)生成過,就不再生成沃暗,而是直接返回月洛。
第三步:解析beanName。
這里有個(gè)beanName的處理細(xì)節(jié)在這里說一下孽锥。如果我們通過注解的方式指定了beanName嚼黔,則直接使用我們指定的beanName。如果我們沒有指定beanName惜辑,Spring會獲取class的名字隔崎,然后生成一個(gè)beanName。
第四步韵丑,解析其他Bean的注解信息爵卒。
借助AnnotationConfigUtils工具類解析注解的其他可選屬性。
主要包括lazyInit屬性撵彻、primary屬性钓株、dependsOn屬性实牡、role屬性、description屬性轴合。
第五步:BeanDefinitionHolder
BeanDefinitionHolder可以看做是對Bean的注解信息的再次封裝创坞。為什么要用BeanDefinitionHolder再次封裝注解信息呢?我們來看一下BeanDefinitionHolder都做了什么受葛。
首先题涨,增加了一個(gè)屬性aliases,這是一個(gè)字符串?dāng)?shù)組总滩。
然后纲堵,會判斷是否需要生成scope作用域的代理,由于默認(rèn)是不生成作用域代理闰渔,直接把BeanDefinitionHolder對象返回了席函。也就是說僅僅是加了一個(gè)屬性aliases。
經(jīng)過BeanDefinitionHolder的處理以后冈涧,bean的定義信息結(jié)構(gòu)如下:
beanName
BeanDefinition對象
aliases字符串?dāng)?shù)組
最后茂附,帶著這個(gè)BeanDefinitionHolder對象,開始執(zhí)行Bean的注冊督弓。
第六步:注冊Bean主流程营曼。
這個(gè)流程是關(guān)鍵點(diǎn),也是最復(fù)雜的愚隧。
首先溶推,注冊的關(guān)鍵類就是這個(gè)BeanDefinitionRegistry。這是一個(gè)接口奸攻,實(shí)現(xiàn)了AliasRegistry接口。BeanDefinitionRegistry接口定義了7個(gè)方法虱痕,都是對BeanDefinition的操作睹耐,包括注冊Bean,刪除Bean等一系列操作部翘。這里我們重點(diǎn)關(guān)注注冊Bean的方法registerBeanDefinition()硝训。
registerBeanDefinition()方法進(jìn)來,首先會進(jìn)行一個(gè)校驗(yàn)新思,這個(gè)校驗(yàn)是一個(gè)合法性校驗(yàn)窖梁。為什么要做這個(gè)校驗(yàn),目前我沒研究清楚夹囚,有清楚的不妨留言交流纵刘,不勝感激。
校驗(yàn)完成以后荸哟,接著會查詢beanDefinitionMap假哎,如果不存在瞬捕,接下來才會注冊bean。如果存在舵抹,會有一系列的邏輯判斷肪虎,因?yàn)槲覀冏⑷氲腷ean通常不會執(zhí)行這段邏輯,這里略過惧蛹。
接下來就是注冊Bean了扇救,注冊Bean有三個(gè)操作。
第一個(gè)操作:把當(dāng)前需要注冊的Bean插入beanDefinitionMap香嗓,這個(gè)map就是Spring存放Bean的容器迅腔。這個(gè)map的實(shí)現(xiàn)是ConcurrentHashMap,初始容量是256陶缺。
鍵值對的key是beanName钾挟,value是beanDefinition。
第二個(gè)操作:把beanName插入beanDefinitionNames饱岸,beanDefinitionNames是一個(gè)ArrayList容器掺出,初始容量也是256。
第三個(gè)操作:把當(dāng)前這個(gè)Bean從manualSingletonNames容器中移除苫费。manualSingletonNames是一個(gè)LinkedHashSet容器汤锨,初始容量是16。
OK百框,到這里闲礼,Bean的注冊就算結(jié)束了。
注冊完Bean以后铐维,還有一些其他細(xì)節(jié)柬泽,比如,把frozenBeanDefinitionNames這個(gè)數(shù)組置為空嫁蛇。至于為什么锨并,目前我還沒有研究清楚。但是這些不影響我們對主流程的把控睬棚。這些細(xì)節(jié)第煮,稍后會再次研究,然后另起一篇文章來說明抑党,這里不再贅述包警。
四,疑惑
DefaultListableBeanFactory定義了下面2個(gè)容器底靠,作用是什么呢害晦?
1,allBeanNameByType
2暑中,singletonBeanNameByType
這2個(gè)容器的實(shí)現(xiàn)是ConcurrentHashMap篱瞎,初始容量為64苟呐。
這個(gè)問題我們后續(xù)研究,大家有興趣俐筋,也可以自己研究一下牵素。