回覆列表
  • 1 # 程式猿W

    目錄:

    學習原始碼的重要性?

    學習Spring原始碼需要基礎嗎?

    怎樣把Spring原始碼在本地執行?

    Bean定義的載入過程

    bean定義載入的流程圖

    總結

    學習原始碼的重要性?

    (1) 可以提升技術功底,Spring原始碼也沉澱了很多年,有非常多的精華所在,不管我們什麼水平,透過不斷的閱讀原始碼,能對我們的技術有很大的提升,並且工作中遇到類似問題的時候,可以借鑑原始碼中是怎麼處理的。

    (2) 深度的掌握技術框架:原始碼看多了,對於新的框架的學習和掌握都是很快的,看下框架的demo,就知道底層是怎麼實現的。

    比如,學習了Spring 中的AOP,就知道底層是用了JDK的動態代理。然後我們學習mybatis的時候,就在想Mybatis 為什麼Service可以直接嗲用Dao介面,就可以直接查詢資料庫了呢 ?其實也是Spring底層對給介面做了動態代理。

    (3) 對線上的問題可以進行快速的定位: 當生產上遇到問題時,能夠快速的進行定位,這個能力可以快速秒殺別人。

    (4) 對面試有很大的好處,特別是BAT大廠,一般都是問道原始碼級別的,你如果不會,可能第一輪就會被刷掉。

    學習Spring原始碼需要基礎嗎?

    答案是肯定的,需要,那麼需要哪些基礎呢?

    (1)Java 的技術功能

    (2) 反射

    (3) 設計模式: 簡單工廠、工廠方法、單例模式、原型模式、代理模式、策略模式、模板方法模式、委派模式、介面卡模式、裝飾器模式、觀察者模式

    (4) Lambda表示式的知識

    怎樣把Spring原始碼在本地執行?

    (1) git clone https://github.com/spring-projects/spring-framework.git

    (2) gradle下載,gradle需要JDK8版本

    (3) 到下載的spring原始碼路徑執行gradle命令,gradlew :spring-oxm:compileTestJava

    (4) 用idea開啟spring原始碼工程,在idea中安裝外掛kotlin,重啟idea

    (5) 把編譯好的原始碼匯入到工程中

    Bean定義的載入過程

    1、首先找到程式的入口

    ① 找到其構造方法:

    ② 呼叫 AnnotationConfigApplicationContext 構造方法,最終會呼叫父類 GenericApplicationContext的無參方法

    上面方法的功能是: 例項化註解的Bean定義掃描器,定義類類路徑下的bean定義掃描器

    3.1 為Bean定義讀取器賦值

    3.1.1 為容器 中註冊系統的Bean定義資訊

    上面程式碼主要是註冊系統的Bean定義資訊,包含以下幾種:

    ① ConfigurationClassPostProcessor

    是一個BeanFactory的後置處理器,主要功能是參與BeanFactory的構建,在這個類中,會解析@Configuration的配置類,解析@ComponentScan、@ComponentScans註解掃描的包,以及解析@Import等註解。

    ② AutowiredAnnotationBeanPostProcessor

    AutowiredAnnotationBeanPostProcessor 實現了BeanPostProcessor,當Spring容器啟動的時候,AutowiredAnnotationBeanPostProcessor 將掃描Spring容器中的所有Bean,當發現Bean中擁有

    @Autowired 註解的時候就會找到與其匹配的Bean,並注入到對應的中去。

    那麼在什麼時候呼叫的呢?我們可以看下debug堆疊;

    RequiredAnnotationBeanPostProcessor 是BeanPostProcessor實現.的,註釋應用於bean屬性的setter方法,它表明 受影響的bean 屬性在配置時是否必須,如果配置了,沒有此bean,則容器就會丟擲一個BeanInitializationException 異常。

    ④ .CommonAnnotationBeanPostProcessor

    CommonAnnotationBeanPostProcessor 這個BeanPostProcessor 透過繼承 InitDestroyAnnotationBeanPostProcessor 對 @PostConstruct 和 @PreDestroy註解的支援,以及對bean的依賴注入@Resource的支援。

    ⑤ EventListenerMethodProcessor

    使用EventListenerMethodProcessor處理器來解析方法上的 @EventListener;

    執行時機: 實在所有Bean都例項化以後執行的

    ④ 建立類路徑下的bean定義掃描器

    上述方法 是註冊預設的掃描規則

    ⑤ 讀取配置類

    上述方法annotatedClasses為我們配置的mainConfig

    annotatedClasses 就是MainConfig

    此時上面主要解析 MainConfig,解析成BeanDefinition物件

    上述的欄位都是什麼意思呢?

    id: Bean的唯一標識名name: 用來為id 建立一個或者多個別名。class : 用來定義類的全限定名(包名 + 類名)parent: 子類bean定義它所引用它的父類的bean。abstract : 預設為false,用來定義bean是否為抽象bean,它表示這個Bean將不會被例項化,一般用於父類Bean,因為父類bean主要供子類bean繼承使用。lazy-init: 用來定義這個bean是否實現懶初始化。如果為true,它將在BeanFactory啟動時初始化所有的單例bean,反之,如果為false,它只在Bean請求使用時才開始建立SingletonBean。autowired : 自動裝配,它定義了Bean 的自動裝配方式。depends-on:依賴物件:這個Bean在初始化時依賴的物件,這個物件會在這個Bean初始化之前建立。init-method: 用來定義Bean的初始化方法,它會在Bean組裝之後呼叫,它必須是一個無參的構造 方法。destroy-method: 用來定義Bean的銷燬方法,它在BeanFactory關閉時呼叫。同樣,它也必須是一個無參 的構造方法。只能適用於單例Bean.factory-method: 定義建立該Bean物件的 工廠方法。factory-bean:定義建立該 Bean物件的工廠類。

    那麼 BeanDefinitionHolder 又是什麼意思呢?

    BeanDefinitionHolder 只是封裝了BeanDefinition物件,並且添加了beanName 和 alias 屬性。

    為什麼這樣設計呢?因為 我們定義bean時,可以定義多個別名的。

    BeanDefinitionRegistry 又是什麼呢?

    BeanDefintion屬性來看,我們並沒有看到id 和 name屬性沒有體現在定義中,原因是ID其左右當前Bean的儲存key註冊到BeanDefinitionRegistry註冊器中。name作為別名key註冊到AliasRegistry註冊中心。最後都是指向其對應的BeanDefinition。

    2、AnnotatedBeanDefinitionReader(Bean定義讀取)

    BeanDefinition 中儲存了Bean的資訊,而BeanDefintiionRegistry是基於ID和name儲存了Bean的定義。從Bean到BeanDefinition然後再註冊到BeanDefintionRegistry整個過程。

    從上圖看出Bean的定義是由AnnotatedBeanDefinitionReader從@Bean的註解中構建出的,然後基於別名註冊到BeanDefinitionRegistry。

    BeanDefintionReader 的結構圖如下:

    2.1 bean定義的載入過程

    (1) org.springframework.context.support.AbstractApplicationContext#refresh

    註冊Bean的 程式碼

    invokeBeanFactoryPostProcessors(beanFactory);

    (2) org.springframework.context.support.AbstractApplicationContext

    #invokeBeanFactoryPostProcessors

    然後例項化 容器初始化 的 ConfigurationClassPostProcessor Bean,然後呼叫其 的postProcessBeanDefinitionRegistry方法

    BeanDefinitionRegistryPostProcessor 這個介面的呼叫分為三部分:

    (1) 呼叫實現PriorityOrdered 排序介面

    (2) 呼叫實現了Ordered排序介面

    (3) 沒有實現介面的呼叫

    這個介面的理解如下: 獲取BeanDefinition 物件,獲取到這個物件就可以獲取這個物件中註冊的 所有BeanDefiniton物件,我們擁有這個物件後,我們就可以對裡面所有的BeanDefinition 物件進行修改。

    org.springframework.context.annotation.ConfigurationClassPostProcessor

    #postProcessBeanDefinitionRegistry 方法

    最終呼叫

    org.springframework.context.annotation.ConfigurationClassParser

    最終呼叫 org.springframework.context.annotation.ConfigurationClassParser

    #doProcessConfigurationClass 方法

    下面方法主要功能如下:

    解析 @PropretySource註解解析@ComponentScan註解解析@Import解析@ImportResource解析@Bean methods處理其他bean定義載入的流程圖總結

    Spring 對註解的處理有兩種方式:

    1、直接將註解Bean註冊到容器中

    可以在初始化容器的時候註冊,也可以在容器建立之後手動呼叫註冊方法向容器中註冊,然後透過手動重新整理容器,使得容器對註冊的註解Bean進行處理

    2、透過掃描指定的 包及其子包下的所有類

    在初始化註解容器的時指定要自動掃描的路徑。如果容器建立以後,如果再向容器中添加註解Bean,則 需要手動呼叫容器掃描的方法,然後手動重新整理容器,使得容器對所註冊的Bean進行處理。

  • 2 # Java架構達人

    如何學習spring原始碼前言

    獲取原始碼

    原始碼的獲取有多種途徑

    GitHub

    spring-framework

    spring-wiki

    可以從GitHub上獲取原始碼,然後自行編譯

    maven

    使用過maven的都知道可以透過maven下載相關的原始碼和相關文件,簡單方便。

    這裡推薦透過maven的方式構建一個web專案。透過對實際專案的執行過程中進行除錯來學習更好。

    如何開始學習前置條件

    如果想要開始學習spring的原始碼,首先要求本身對spring框架的使用基本瞭解。明白spring中的一些特性如ioc等。瞭解spring中各個模組的作用。

    確定目標

    首先我們要知道spring框架本身經過多年的發展現在已經是一個龐大的家族。可能其中一個功能的實現依賴於多個模組多個類的相互配合,這樣會導致我們在閱讀程式碼時難度極大。多個類之間進行跳躍很容易讓我們暈頭轉向。

    所以在閱讀spring的原始碼的時候不能像在JDK程式碼時一行一行的去理解程式碼,需要把有限的精力更多的分配給重要的地方。而且我們也沒有必要這樣去閱讀。

    在閱讀spring某一功能的程式碼時應當從一個上帝視角來總覽全域性。只需要知道某一個功能的實現流程即可,而且幸運的是spring的程式碼規範較好,大多數方法基本都能見名知意,這樣也省去了我們很多的麻煩。

    利用好工具

    閱讀程式碼最好在idea或者eclipse中進行,這類IDE提供的很多功能很有幫助。

    在閱讀時配合spring文件更好(如果是自行編譯原始碼直接看註釋更好)。

    筆記和複習

    這個過程及其重要,我以前也看過一些spring的原始碼,但是好幾次都是感覺比較吃力在看過一些後就放棄了。而由於沒有做筆記和沒有複習的原因很快就忘了。下次想看的時候還要重新看一遍,非常的浪費時間。

    下面以IOC為例說明下我是怎麼看的,供參考。

    IOC入口:ApplicationContext

    在研究原始碼時首先要找到一個入口,這個入口怎麼選擇可以自己定,當一定要和你需要看的模組有關聯。

    比如在IOC中,首先我們想到建立容器是在什麼過程?

    在程式啟動的時候就建立了,而且在啟動過程中大多數的bean例項就被注入了。

    那問題來了,在啟動的時候是從那個類開始的呢?熟悉spring的應該都知道我們平時在做單元測試時如果要獲取bean例項,一個是透過註解,另外我們還可以透過構建一個ApplicationContext來獲取:

    在例項化ApplicationContext後既可以獲取bean,那麼例項化的這個過程就相當於啟動的過程了,所以我們可以將ApplicationContext當成我們的入口。

    ApplicationContext是什麼

    首先我們要明白的事我們平時一直說的IOC容器在Spring中實際上指的就是。

    如果有看過我之前手寫Spring系列文章的同學肯定知道在當時文章中充當ioc容器的是BeanFactory,每當有bean需要注入時都是由BeanFactory儲存,取bean例項時也是從BeanFactory中獲取。

    那為什麼現在要說ApplicationContext才是IOC容器呢?

    因為在spring中BeanFactory實際上是被隱藏了的。ApplicationContext是對BeanFactory的一個封裝,也提供了獲取bean例項等功能。因為BeanFactory本身的能力實在太強,如果可以讓我們隨便使用可能會對spring功能的執行造成破壞。於是就封裝了一個提供查詢ioc容器內容的ApplicationContext供我們使用。

    如果專案中需要用到ApplicationContext,可以直接使用spring提供的註解獲取:

    如何使用ApplicationContext

    如果我們要使用ApplicationContext可以透過new該類的一個例項即可,定義好相應的xml檔案。然後透過下面的程式碼即可:

    ApplicationContext的體系

    瞭解一個類,首先可以來看看它的繼承關係來了解其先天的提供哪些功能。然後在看其本身又實現了哪些功能。

    上圖中繼承關係從左至右簡要介紹其功能。

    ApplicationEventPublisher:提供釋出監聽事件的功能,接收一個監聽事件實體作為引數。需要了解的可以透過這篇文章:事件監聽ResourcePatternResolver:用於解析一些傳入的檔案路徑(比如ant風格的路徑),然後將檔案載入為resource。HierarchicalBeanFactory:提供父子容器關係,保證子容器能訪問父容器,父容器無法訪問子容器。ListableBeanFactory:繼承自BeanFactory,提供訪問IOC容器的方法。EnvironmentCapable:獲取環境變數相關的內容。MessageSource:提供國際化的message的解析

    配置檔案的載入

    Spring中每一個功能都是很大的一個工程,所以在閱讀時也要分為多個模組來理解。要理解IOC容器,我們首先需要了解spring是如何載入配置檔案的。

    縱覽大局

    idea或者eclipse提供了一個很好的功能就是能在除錯模式下看到整個流程的呼叫鏈。利用這個功能我們可以直接觀察到某一功能實現的整體流程,也方便在閱讀程式碼時在不同類切換。

    以載入配置檔案為例,這裡給出整個呼叫鏈。

    上圖中下面的紅框是我們寫的程式碼,即就是我們應該開始的地方。下面的紅框就是載入配置檔案結束的地方。中間既是整體流程的實現過程。在閱讀配置檔案載入的原始碼時我們只需要關心這一部分的內容即可。

    需要知道的是這裡展示出來的僅僅只是跟這個過程密切相關的一些方法。實際上在這個過程中還有需要的方法被執行,只不過執行完畢後方法棧彈出所以不顯示在這裡。不過大多數方法都是在為這個流程做準備,所以基本上我們也不用太在意這部分內容

    refresh()

    前面的關於的建構函式部分沒有啥好說的,在建構函式中呼叫了一個方法。該方法非常重要,在建立IOC容器的過程中該方法基本上是全程參與。主要功能為用於載入配置或這用於重新整理已經載入完成的容器配置。透過該方法可以在執行過程中動態的加入配置檔案等:

    AbstractApplicationContext#refresh

    方法主要用於設定一些日誌相關的資訊,比如容器啟動時間用於計算啟動容器整體用時,以及設定一些變數用來標識當前容器已經被啟用,後續不會再進行建立。

    ```

    該方法首先判斷是否已經例項化好BeanFactory,如果已經例項化完成則將已經例項化好的BeanFactory銷燬。

    然後透過new關鍵字建立一個BeanFactory的實現類例項,設定好相關資訊。方法用於設定是否運行當beanName重複是修改bean的名稱(allowBeanDefinitionOverriding)和是否執行迴圈引用(allowCircularReferences)。

    ```

    ```

    就是讀取配置檔案並將其內容解析為一個Document的過程。解析xml一般來說並不需要我們特別的去掌握,稍微有個瞭解即可,spring這裡使用的解析方式為Sax解析,有興趣的可以直接搜尋相關文章,這裡不進行介紹。下面的才是我們需要關注的地方。

    registerBeanDefinitions(Document, Resource)

    在進入該方法後首先建立了一個的例項,這和之前的用於讀取xml的reader類一樣,只不過該類是用於從xml檔案中讀取。

    Environment

    在上面的程式碼中給Reader設定了Environment,這裡談一下關於Environment。

    Environment是對spring程式中涉及到環境有關的一個描述集合,主要分為profile和properties。

    profile是一組bean定義的集合,透過profile可以指定不同的配置檔案用以在不同的環境中,如測試環境,生產環境的配置分開。在部署時只需要配置好當前所處環境值即可按不同分類載入不同的配置。

    profile支援xml配置和註解的方式。

    也可以透過註解配置:

    然後在啟動時根據傳入的環境值載入相應的配置。

    properties是一個很寬泛的定義,其來源很多如properties檔案,JVM系統變數,系統環境變數,JNDI,servlet上下文引數,Map等。spring會讀取這些配置並在environment介面中提供了方便對其進行操作的方法。

    總之就是設計到跟環境有關的直接來找Environment即可。

    handler

    先來看一個XML檔案。

    上面是xml中根元素定義部分,可能平時並沒有太多人注意。其屬性中的xmlns是XML NameSpace的縮寫。namespace的作用主要是為了防止在xml定義的節點存在衝突的問題。比如上面聲明瞭mvc的namespace:。在xml檔案中我們就可以使用mvc了:

    而實際上在spring中還根據上面定義的namespace來準備了各自的處理類。這裡因為解析過程就是將xml定義的每一個節點取出根據配置好的屬性和值來初始化或註冊bean,為了保證程式碼可讀性和明確的分工,每一個namespace透過一個專有的handler來處理。

    跟蹤方法,最終來到類的構造方法中。

    可以看到預設的handler是透過一個本地檔案來進行對映的。該檔案存在於被依賴jar包下的META-INF資料夾下的spring.handlers檔案中。

    這裡只是展示了beans包下的對映檔案,其他如aop包,context包下都有相應的對映檔案。透過讀取這些配置檔案來對映相應的處理類。在解析xml時會根據使用的namespace字首使用對應的handler類解析。這種實現機制其實就是所謂的SPI(Service Provider Interface),目前很多的應用都在實現過程中使用了spi,如dubbo,mysql的jdbc實現等,有興趣的可以取了解一下。

    doRegisterBeanDefinitions(Element)

    到這一步中間省略了一個方法,很簡單沒有分析的必要。

    delegate

    這裡的delegate是對BeanDefinitionParse功能的代理,提供了一些支援解析過程的方法。我們可以看到上面有一個重新建立delegate同時又將之前的delegate儲存的程式碼。註釋上說是為了防止巢狀的beans標籤遞迴操作導致出錯,但是註釋後面又說這並不需要這樣處理,這個操作真的看不懂了,實際上我認為即使遞迴應該也是沒有影響的。還是我理解錯了?

    建立好delegate後下面的if語句塊就是用來判斷當前載入的配置檔案是否是當前使用的profile指定的配置檔案。上面在介紹Environment的時候已經介紹過來,如果這裡載入的配置檔案和profile指定的不符則直接結束。

    public boolean isDefaultNamespace(String namespaceUri) {//BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));}

    ```

    接下來的可以算是我們本次目標最重要的一步了,前面所有的流程都是給這一步鋪墊。透過該方法將解析出來的BeanDefinition註冊到容器中,方便例項化。

    方法接收兩個引數holder和registry,如果有看過我手寫系列的文章(IOC篇)應該知道,當時為了將bean定義和容器關聯起來以及為了將的功能簡化,所以我們定義了一個介面用於將註冊到容器中和從容器中取,這裡的registry功能也是一樣的。(被實現,而實際就是容器)

    而且可以看到實際上這裡也是透過beanName來區分BeanDefinition是否重複(實際上肯定是我仿的spring的(笑)),只不過為了執行名稱相同的BeanDefinition註冊提供了alias,之前在實現ioc時沒有實現這一步。

    在方法的最後一步實際上是註冊了一個listener,在一個被註冊後觸發,只不過上spring中實際觸發方法是一個空方法,如果我們需要在註冊完成後做一些什麼工作可以直接繼承後實現方法即可。

    到這裡基本上關於BeanDefinition的載入就完成了,後面就是重複上面的流程載入多個配置檔案。

    小結

    本節主要介紹了我關於學習spring原始碼的一些方法,以及以spring的BeanDefinition的載入為例分析了其整體的流程,希望對大家能有所幫助。還要提的是spring原始碼很複雜,如果只是開斷點一路除錯下去肯定是不夠的,看的過程中需要多做筆記。由於該文章內容較多以及本人水平問題,文章中可能會存在錯誤,如果有可以指出來方便修改。

  • 中秋節和大豐收的關聯?
  • 鑽石顏色怎麼選?才可以買到滿意的結婚鑽戒?你知道嗎?