? ? Spring是目前開發(fā)最為重要的后端框架,那么它運(yùn)行的基本邏輯是什么刑棵?又如何編寫一個自定義的Spring容器砚婆?
Spring是如何調(diào)用的意蛀?
? ? 我們在使用Spring的時(shí)候淆珊,第一步應(yīng)該是加載配置夺饲,常見的有XML配置文件形式和注解形式的,本次采用最常用的注解形式來編寫自定義Spring容器套蒂。從Spring加載的方式看钞支,需要一個上下文加載器茫蛹,把配置入口類作為參數(shù)傳入操刀。然后通過getBean方法獲取到bean實(shí)例,然后就可以調(diào)用實(shí)例里的方法了婴洼。如圖1所示:
Spring是如何加載文件的骨坑?
? ? Spring中定義指定掃描包路徑的注解是@ComponentScan,在指定了掃描包路徑后需要獲取哪些文件需要被加載柬采,那么這個時(shí)候就需要@Component注解了欢唾,在自定義注解中也同樣創(chuàng)建這兩個注解,注解中都定義一個參數(shù)粉捻,@ComponentScan參數(shù)代表掃描包的路徑礁遣,@Component參數(shù)代表beanName。如圖2所示肩刃,輸出的是@ComponentScan中的包路徑:
? ? 對于Java來說祟霍,加載的是class文件杏头,因此需要獲取class文件的相對路徑,需要把 . 轉(zhuǎn)換成 / 沸呐,然后再讀取文件醇王,獲取到文件后,再利用loadClass方法來反射構(gòu)造類崭添。用搜索@ComponentScan注解的方式對文件遍歷后寓娩,就能獲取到所有含@Component注解的類了,如圖3:
? ? 我們在進(jìn)行g(shù)etBean方法獲取bean實(shí)例的時(shí)候呼渣,傳入的是一個beanName棘伴,@Component在沒有傳入指定beanName的時(shí)候默認(rèn)使用的是類的小寫駝峰,可以調(diào)用jdk自帶的方法來生成徙邻,如圖4:
? ? BeanDefinition顧名思義是Bean定義排嫌,Spring在加載后把bean相關(guān)的所有信息都打包成一個BeanDefinition,自定義spring中就簡單點(diǎn)缰犁,里面的參數(shù)只定義兩個淳地,Class type代表bean所在類和String scope代表作用域。scope作用域一般分為單例和原型的帅容,單例bean不會重復(fù)創(chuàng)建颇象,而原型bean在每次注入或者通過上下文獲取的時(shí)候都會創(chuàng)建。同樣定義一個@Scope注解并徘,默認(rèn)singleton遣钳。為了后續(xù)能方便通過beanName獲取到Beandefinition,這邊同時(shí)定義一個beandefinitionMap麦乞,以beanName作為key蕴茴,BeanDefinition作為value。如圖5:
? ? ? ? 到目前為止姐直,模擬Spring對class文件的掃描就完成了倦淀。
Spring通過getBean獲取實(shí)例
? ? 通過圖1可以看到,我們是通過getBean方法声畏,傳入一個beanName來獲取bean實(shí)例的撞叽。要生成實(shí)例,就需要通過反射創(chuàng)建插龄,這時(shí)候就需要用到我們的BeanDefinition愿棋,首先需要判斷是單例還是原型,如果是原型的均牢,直接創(chuàng)建糠雨,如果是單例的,需要和之前創(chuàng)建的實(shí)例進(jìn)行比較徘跪,這時(shí)候就需要引入另一個實(shí)例參數(shù)singletonObjects甘邀,以beanName作為key砂竖,對應(yīng)創(chuàng)建的實(shí)例作為value,從這個參數(shù)中獲取不到實(shí)例才需要進(jìn)行創(chuàng)建鹃答,如圖6:
? ? 使用反射的形式通過class創(chuàng)建實(shí)例乎澄,自定義Spring容器采用無參構(gòu)造器構(gòu)建實(shí)例即可,如圖7:
? ??
? ??然后在掃描的時(shí)候测摔,遍歷一遍beanDefinitionMap置济,對其中的singleton生成實(shí)例,并塞進(jìn)singletonObjects中锋八,如圖8:
Spring的BeanPostProcessor
????Spring的BeanPostProcessor是在bean對象在實(shí)例化或者依賴注入完畢后浙于,調(diào)用自己方法的前后添加自己的邏輯,這個就是Spring的AOP挟纱,所以我們需要自定義一個BeanPostProcessor類羞酗,并編寫前置處理器方法和后置處理器方法,如圖9:
? ? 自定義的Spring在掃描的時(shí)候就應(yīng)該關(guān)注到BeanPostProcessor了紊服,正程垂欤可以通過new出你的BeanPostProcessor來執(zhí)行前置或者后置方法,但是也可以在掃描的時(shí)候就把實(shí)現(xiàn)了BeanPostProcessor的類所生成的實(shí)例存入beanPostProcessorList中以供在創(chuàng)建實(shí)例的時(shí)候執(zhí)行響應(yīng)的方法欺嗤,如圖10:
? ? 下圖11為如何使用自定義的BeanPostProcessor:
Spring的InitialzingBean
? ? Spring的InitializingBean是用來執(zhí)行相關(guān)的業(yè)務(wù)操作参萄,只需要實(shí)現(xiàn)它的afterPropertiesSet方法即可,在創(chuàng)建bean實(shí)例的時(shí)候煎饼,只需把它放在BeanPostProcessor的前置處理器和后置處理器之間即可讹挎,使用instance of來判斷是否實(shí)現(xiàn)了InitializingBean方法,如圖12和圖13:
Spring的@Autowired
? ? Spring中比較常用的還有一個@Autowired吆玖,用來做依賴注入筒溃,里面有個默認(rèn)的required參數(shù)表示是否開啟自動裝配,默認(rèn)為true沾乘,我們自定義的@Autowired就不加參數(shù)了怜奖,注解放置的位置也固定在字段上,就按照自動裝配去加載意鲸。
? ? 在創(chuàng)建實(shí)例的時(shí)候烦周,對實(shí)例中的字段逐個判斷有沒有@Autowired注解尽爆,若有怎顾,對field進(jìn)行set操作,將field對應(yīng)的bean實(shí)例注入進(jìn)來漱贱,替換掉之前的實(shí)例槐雾,調(diào)用的方法就是之前提到的getBean操作,但在自定義的Spring中簡化后就fieldName作為beanName傳入幅狮,后續(xù)可以自行優(yōu)化募强,如圖14:
? ? 此時(shí)就已經(jīng)完成了自定義Spring框架的基礎(chǔ)流程株灸。
? ? 源碼下載地址:https://github.com/LuoChen1996/identitify_spring.git