當5G來臨,當211高校已經開啓人工智能課程,當甲骨文大批量裁員,大家的心是否像我一樣爲之一顫呢?當科技不斷髮展,技術迅速迭代,程序員愈發年輕化的今天,而作爲我們已經步入中年的程序員來說路在何方?當我們逐漸老去,我們不能指望企業家的憐憫,當大批年輕化的程序員涌入互聯網大潮時,他們的思維,他們的體能,甚至他們的能力都遠超於我們,我們又該何去何從?職場不相信眼淚,更不會同情,唯有修煉內功,修煉職場硬實力,當然如果硬實力不行,也只能來點軟的了…
作爲程序員不懂高併發、JVM優化、系統內核、大數據、框架源碼… …,整天寫CRUD,也許很快就會被時代所淘汰。
下面讓我帶你抽絲剝繭spring源碼。會從spring ioc容器核心結構、spring ioc注入執行流程的脈絡講起,包括什麼是BeanFactory和FactoryBean;什麼是BeanDefinition及其有哪些重要的實現類;什麼是BeanPostProcessor、BeanFactoryPostProcessor、ImportSelector、ImportBeanDefinitionRegistrar,以及如何應用他們來實現對spring的擴展等等,逐步深入細節,當然spring源碼非常龐大難懂,本文是從spring ioc講起,重點是梳理spring ioc注入的脈絡,之後的博文會一點點抽絲剝繭的對spring內部處理細節做逐步分析講解,包括Spring aop、spring Tx等等,來逐步解密spring。
帶您初識spring容器模型
在學習spring ioc源碼之前,有必要先了解下其核心類的含義及作用。上面是一個粗略的spring bean工廠內部存儲,鑑於spring工廠比較龐大,上圖也只畫出了一部分比較重要的核心類,但是即便這樣這個圖片也已經看不清了,但是我會帶您一個個講解說明。
初始化spring容器測試代碼:
-
首先既然是spring ioc,它的最重要的作用就是管理bean,所以我們猜測肯定是會有一個bean工廠的,這個是很多框架都有的(如mybatis),上來就會來個工廠。這就是 BeanFactory,在spring中默認的實現類爲DefaultBeanFactory,DefaultBeanFactory作爲bean工廠的默認實現,主要提供了bean的註冊,bean的獲取取。當然bean的註冊和獲取的具體實現是由其子類AbstractBeanFactory和DefaultSingletonBeanRegistry類完成。
其實在本文開始的時候我也提到了FactoryBean,那麼FactoryBean又是什麼的?它是一個spring提供的一個接口,其實大家目前可以簡單粗暴的理解爲是一個特殊的bean,在某些 情況下bean的實例化過程比較複雜,可以實現FactoryBean接口重寫getObject()方法來實現一個複雜bean的實例化,實現FactoryBean接口會在spring工廠中注入2個bean,實現 類本身會對應一個bean,beanName爲&+類名(首字母小寫),還有一個就是getObject()返回的bean,也就是我們自己自定義的bean,beanName爲返回的Object類的類名 (首字母小寫)。如我們比較熟悉的mybatis就是應用了spring的這個FactoryBean這個擴展類,具體mybatis中的哪個類應用了,大家自己想吧。 -
BeanDefinition:顧名思義,這個類是描述bean的接口。有兩個重要的實現類分別是RootBeanDefinition和AnnotatedGenericBeanDefinition。RootBeanDefinition是用來定 義Spring內部的bean,如ConfigurationClassPostProcessor;AnnotatedGenericBeanDefinition是用來定義我們自定義的bean,也就是我們注入spring容器,需要spring管理的bean。
-
說完BeanDefinition,我們來認識下bean的元數據定義類。ClassMetadata:定義類的元數據,其中包括getClassName、isInterface、isAnnotation方法;MethodMetadata:定義方法的元數據;AnnotationMetadata:定義註解類元數據信息;
-
Spring初始化時bean是怎麼存儲的?答案是存儲在DefaultListableBeanFactory類的集合中,其中BeanDefinitionMap是存儲了beanName和BeanDefinition的映射,beanDefinitionNames存儲了beanName,見下圖:
但請注意此時bean還沒有實例化,實例化後,bean是存儲在DefaultSingletonBeanRegistry類中的singletonObjects中,這個我們在之後分析源碼的時候會詳細說。
-
AnnotatedBeanDefinitionReader:這個類是spring容器初始化的時候,在DefaultListableBeanFactory初始化之後就會實例化的一個類。它主要的作用是向spring bean工廠中註冊我們自定義的bean,但是這個類其實是委託了BeanDefinitionReaderUtils類去調用DefaultListableBeanFactory類去完成bean的註冊的,可見DefaultListableBeanFactory的重要性。
-
BeanDefinitionHolder:封裝了beanName和BeanDefinition,不用關注。
-
BeanFactoryPostProcessor:bean工廠的後置處理器,可以插手bean工廠初始化過程,實現這個接口會得到beanFactory實例,也就是可以操作bean工廠了。至於什麼時候會執行其實現類,那是後面博文會講解的內容。
-
BeanPostProcessor:bean的後置處理器。會插手bean實例化的過程,實現此接口可以操縱容器中正在初始化的bean,也就是說可以對bean屬性做修改。
-
BeanDefinitionRegistryPostProcessor:是個接口,實現了BeanFactoryPostProcessor接口,定義了postProcessorBeanDefinitionRegistry(registry)方法,也就是擴展了BeanFactoryPostProcessor接口,spring內部ConfigurationClassPostProcessor類實現了這個接口,去完成bean的解析。
-
實例化AnnotatedBeanDefinitionReader時,同時spring會向容器中注入7個spring自帶的bean處理器。這些處理器在spring容器中都起着重要作用。這個也留待我們之後的博文中進行深入講解。
-
ClassPathBeanDefinitionScanner:主要用於包的掃描工作。後續博文中會詳細說。
以上簡要帶大家認識了spring ioc注入過程中涉及到的一些比較重要的spring核心類。
Spring ioc初始化流程
下面我們就以註解方式初始化spring爲例講解下spring容器初始化過程。
Spring註解方式啓動:實例化AnnotationConfigApplicationContext,傳入需要掃描的配置類或需要注入的beanclass。接着我們就來進入主題,看下源碼入口:
實例化父類構造方法
如上圖,首先會執行其父類的構造方法,正如我這個方法上註釋的說明,主要實例化三個類,我們分別看下:
GenericApplicationContext:主要實例化了beanFactory,默認爲DefaultListableBeanFactory。
AbstractApplicationContext:主要實例化PathMatchingResourcePatternResolver。這個類我在第一部分沒有拿出來說明,其實可以理解它是資源文件解析器,提供瞭解析資源文件的功能。
DefaultResourceLoader:實例化類加載器,其實就是APPClassLoader的實例。
執行完父類構造方法後,我們看下spring容器中的變化,即向spring容器中實例化了:
無參構造方法
父類構造方法執行結束,我們就來看下主體方法中的第一個方法this()。它會調用無參的構造方法:
通過上面對代碼的註解,你應該大致瞭解了這個構造方法做了什麼。下面就在來看下里面的實現。
這個類最終主要會調用AnnotationConfigUtils.registerAnnotationConfigProcessors()方法,也就是AnnotatedBeanDefinitionReader會委託AnnotationConfigUtils類做一些事情,那麼我們就看下registerAnnotationConfigProcessors()方法做了哪些事情。
正如這段代碼中我註釋所寫的那樣,主要是向bean工廠中註冊spring內部自定義的bean。其中這些bean有的是實現了BeanPostProcessor、BeanFactoryPostProcessor接口的,這兩個接口我們在第一部分中做過說明。被註冊的6個bean在後續的操作中都有着各自的作用,這些類會在後續的博文中做詳細的介紹。
這裏每個bean都會封裝成RootBeanDefinition(這個我們在第一部分也做過說明,RootBeanDefinition是封裝spring內部自定義的bean的),最終會調用registry.registerBeanDefinition(beanName, definition)方法向容器中註冊bean。Registry其實就是DefaultListableBeanFactory的實例,那麼也就是調用DefaultListableBeanFactory類的registerBeanDefinition()方法:
上面是當前會執行的代碼部分,也就是會將那6個bean都註冊到beanDefinitionMap中。這也就是實例化AnnotatedBeanDefinitionReader類主要做的事情。
我們在來看下現在spring容器中的變化,其實就是bean工廠中註冊了7個spring自定義的bean:
上面說了構造函數中的this.reader = new AnnotatedBeanDefinitionReader(this)方法,接下來說下構造函數中ClassPathBeanDefinitionScanner實例化的過程。
其實這個實例化的過程比較簡單,除了實例化ClassPathBeanDefinitionScanner類之外,主要就是註冊了幾個註解:
暫時可以先不用管這部分,不重要。
此時就完成了spring容器初始化的第一步,再讓我們來看下spring容器目前的情況:
register(annotatedClasses)註冊自定義bean
接下來就要處理我們需要spring容器來管理的beanclass了。其實就是調用AnnotatedBeanDefinitionReader的register方法,其中參數AnnotatedClasses是我們傳入的需要注入的bean,可以是一個帶有@ComponentScan註解的配置類,也可以是一個bean集合,也可以是一個單個bean,所以此處做了循環操作處理。
最終會調用doRegisterBean()方法:
這個方法首先會調用shouldSkip()方法對beanclass做校驗,具體校驗邏輯見我對代碼的註解說明:
接着會調用AnnotationConfigUtils.processCommonDefinitionAnnotations()方法,在這個方法中會給這個bean實例賦予屬性,主要是通過這個beanclass上的註解來賦值,會看是否的@Lazy的、@Primary的值、@DependsOn的值、@Role的值、@Description的值,beanclass上存在這些註解就會取值並賦給bean實例。具體實現我們在之後的博文中會詳細分析。
下面是這個方法中最重要的部分,也就是最終的注入。這個如下面第二個圖所示,它最終也會調用registry.registerBeanDefinition()方法,也就是DefaultListableBeanFactory類的registerBeanDefinition()方法,最終將annotationClass注入到beanDefinitionMap和BeanDefinitionNames中。這個方法上面有說過,這裏就不在說明了,這個方法中具體的細節會在之後的博文中詳細分析。
至此我們的register()方法就處理脈絡就說完了,我們在來看下此時的bean工廠的變化:
其實就是如我上面說的那樣將annotationClass(App.class)注入到了我們的bean工廠中。
refresh()
上面主要說了spring ioc容器啓動過程的前兩個方法,下面我們來看下spring ioc容器啓動的最核心最重要的方法refresh()。
下面就讓我們看下refresh()方法都做了什麼?
上面是refresh()代碼的主體部分,spring ioc容器初始化都會在這裏完成,其內部代碼非常複雜,這裏我們就先了解下其處理脈絡,看下每個方法都做了什麼,在之後的博文中會詳細講解其處理細節。
prepareRefresh()
prepareRefresh():這個方法比較簡單,主要是設置了啓動時間、啓動標識等操作,不是重點,先不用關注。
obtainFreshBeanFactory()
這個方法顧名思義,就是重新獲取beanFactory實例,這個方法沒有什麼可說的,就是獲取了之前我們初始化的beanFactory實例DefaultListableBeanFactory。
GenericApplicationContext:
prepareBeanFactory()
準備bean工廠初始化環境。主要是注入spring內部bean、註冊Aware相關接口。
postProcessBeanFactory()
空方法。
invokeBeanFactoryPostProcessors()
這是比較重要的方法之一。其內部處理比較複雜,當然其處理方式是非常值得學習的,想要知道這個方法做了什麼嗎?下節我將帶大家一起分析本方法和後續的處理方法的脈絡部分。
最後
Spring的設計思想及源碼不是一朝一夕能弄透的,需要長期的積累和思辨。我已開啓Spring系列源碼分析的教程,有興趣的小夥伴請關注我。如果本文對您有價值,不要忘了點贊、留言。