之前寫過的很多spring文章,都是基於應用方面的,這次的話,就帶大家來一次對spring的源碼追蹤,看一看spring到底是怎么進行的初始化,如何創建的bean,相信很多剛剛接觸spring的朋友,或者沒什么時間的朋友都很想知道spring到底是如何工作的。
首先,按照博主一貫的作風,當然是使用最新的spring版本,這次就使用spring4.2.5...其次,也是為了方便,采用spring-boot-1.3.3進行追蹤,和spring 4.2.5是相同的。
不用擔心框架不同,大家如果是使用的xml方式進行配置的話,可以去你的ContextListener里面進行追蹤,spring-boot只是對 spring所有框架進行了一個集成,如果實在進行不了前面幾個步驟的話,可以從文章第6步的AbstractApplicationContext開始看起, 這里就是spring最最重要的部分。
1、默認的spring啟動器,DemoApplication:
該方法是spring-boot的啟動器,我們進入。
2、進入了SpringApplication.java:
這里創建了一個SpringApplication,執行run方法,返回的是一個ConfigurableApplicationContext,這只是一個接口而已,根據名稱來看,稱作可配置的應用程序上下文。
3、我們不看new SpringApplication(Sources)過程了,有興趣可以自己研究一下,里面主要是判斷了當前的運行環境是否為web,當然,博主這次的環境是web,然后看run:
try語句塊內的內容最為重要,因為創建了我們的context對象,此時需要進入的方法為
context = createAndRefreshContext(listeners, applicationArguments)
4、 接着往下看,看到context = createApplicationContext這行,進入,因為我們剛剛在創建SpringApplication時並沒有給 this.applicationContextClass賦值,所以此時this.applicationContextClass = null,那么便會創建指定的兩個applicationContext中的一個,返回一個剛剛創建的context,這個context便是我們的基 礎,因為門現在為web環境,所以創建的context為 AnnotationConfigEmbeddedWebApplicationContext。
5、第4步創建了一個context,需要指出的是,context里面默認帶有一個beanFactory,而這個beanFactory的類型為DefaultListableBeanFactory。
然后繼續看我們的createAndRefreshContext方法,忽略別的代碼,最重要的地方為refresh(context):
6、進入refresh(context),不管你進入那個實現類,最終進入的都是AbstractApplicationContext.java:
該方法中,我們這次需要注意的地方有兩個:
1、invokeBeanFactoryPostProcessors(beanFactory);
2、finishBeanFactoryInitialization(beanFactory);
兩處傳入的beanFactory為上面的context中的DefaultListableBeanFactory。
7、進入invokeBeanFactoryPostProcessors(beanFactory):
然后找到第98行的invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry),該方法看名字就是注冊bean,進入。
8、 該方法內部有一個for循環,進入內部方法 postProcessor.postProcesBeanDefinitionRegistry(registry),此時傳入的registry就是我們context中的beanfactory,因為其實現了BeanDefinitionRegistry接口。而此時的postProcessor實現類為ConfigurationClassPostProcessor.java。
9、進入之后直接看最后面的一個方法,名稱為processConfigBeanDefinitions(registry),翻譯過來就是配置beanDefinitions的流程。
10、在processConfigBeanDefinitions(registry)里,314行創建了一個parser解析器,用來解析bean。並在第321行進行了調用,那么我們進入parse方法。
11、進入parse方法之后,會發現內層還有parse方法,不要緊,繼續進入內層的parse,然后會發現它們均調用了processConfigurationClass(ConfigurationClass configClass)方法:
12、 在processConfigurationClass(ConfigurationClass configClass)方法內,找到do循環,然后進入doProcessConfigurationClass方法,此時,便會出現許多我們常用的注 解了,spring會找到這些注解,並對它們進行解析。例如第268行的componentScanParser.parse方法,在這里會掃描我們的注 解類,並將帶有@bean注解的類進行registry。
13、進入 componentScanParser.parse,直接進入結尾的scannner.doScan,然后便會掃描basepackages,並將掃描 到的bean生成一個一個BeanDefinitionHolder,BeanDefinitionHolder中包含有我們bean的一些相關信息、以 及spring賦予其的額外信息,例如別名:
14、 雖然已經創建了BeanDefinitionHolder,但並沒有添加到我們的beanFactory中,所以需要執行263行的 registerBeanDefinition(definitionHolder, this.registry),進入后繼續跳轉:
然后看registry.registerBeanDefinition方法,因為我們的beanFactory為DefaultListableBeanFactory,所以進入對應的實現類。
15、在進入的registry.registerBeanDefinition方法中,關鍵點在851行或871行:
this.beanDefinitionMap.put(beanName, beanDefinition);
這個方法將掃描到的bean存放到了一個beanName為key、beanDefinition為value的map中,以便執行DI(dependency inject)。
16、現在我們回到第6步的第二條分支,此處是非懶加載的bean初始化位置,注意,我們之前只是對bean的信息進行了獲取,然后創建的對象為BeanDefinition,卻不是bean的實例,而現在則是創建bean的實例。
進入方法后找到829行的getBean(weaverAwareName):
17、getBean => getBeanFactory.getBean => doGetBean,然后找到306行的createBean,這里不講語法,不要奇怪為什么這個createBean不能進入實現代碼。
18、這之后的代碼都比較容易追蹤,直接給一條調用鏈:
doCreateBean(482) => createBeanInstance(510) => autowireConstructor(1034,1046) => autowireConstructor(1143) => instantiate(267) => instantiateClass(122) => newInstance(147)
括號內的數字代表行號,方便大家進行追蹤,最后看到是反射newInstance取得的對象實例:
平時總說spring反射獲取bean,其實也就是聽別人這么說而已,還是自己見到才踏實,萬一別人問你是不是通過Class.forName獲取的呢?
19、屬性注入,位於第18條的doCreateBean方法內,找到第543行,populateBean便譯為填充Bean,進入后便能看到和我們平時代碼對應的條件了,例如byType注入、byName注入:
這里還沒有進行依賴注入,僅僅是准備一些必要的信息,找到1214行的ibp.postProcessPropertyValues方法
20、這里有很多實現類可以選擇,因為博主平時是使用@Autowired注解,所以這里選擇AutowiredAnnotationBeanPostProcessor,如果你使用@Resource的話,就選擇CommonBeanPostProcessor:
21、進入該方法后,首先獲取一些元信息metadata,通過findAutowiringMetadata獲取,然后調用metadata.inject進行注入:
22、繼續進入inject方法后,繼續找到88行的element.inject方法並進入,實現類選擇AutowiredFieldElement,該類是一個內部類:
在這個方法中,最重要的內容在第567~570行內,我們可以看到,這里其實也就是jdk的反射特性。
至此,spring的 bean初始化->注入 便完成了。
這次的博客內容很長(其實是自己追蹤代碼時間太久),感謝大家耐心看完,能有所收獲的話便最好不過了。另外,若是有什么補充的話歡迎進行回復。