Spring Framework之再探Core Container(中)

特別說明

這是一個由simviso團隊進行的關於Spring Framework 5.2版本內容分享的翻譯文檔,分享者是Spring Framework 5.2項目leader。
視頻第一集:https://www.bilibili.com/vide...java

視頻第二集:https://www.bilibili.com/vide...git

視頻翻譯文字版權歸 simviso全部,未經受權,請勿轉載
圖片描述
參與人員名單:
圖片描述
順帶推薦一個專業的程序員後端微信羣的圈子:
圖片描述程序員

1. GenericApplicationContext

出於多種目的,特別是在GenericApplicationContext這裏,咱們專門提供了Kotlin擴展。你不須要特地引入它們(Kotlin支持),不須要額外的步驟,它們已經成爲Spring Framework核心中的一部分。so,不管你使用的是 Spring context 5.0、5.一、5. 2 中任何版本,你都會自動得到帶有 Kotlin 擴展的 GenericApplicationContext。若是你選擇使用 Kotlin 來進行開發,那麼它們就會被 Kotlin 編譯器檢測編譯。
圖片描述
來看這個GenericApplicationContext,它的處理方式是否是看起來很熟悉,但它是使用Kotlin來寫的。能夠看到,圖中下面的和上面的版本明顯有一些區別。舉個例子來說,咱們撇開Bar.class,而後來講這裏該如何去建立一個bar實例。咱們只須要擁有一個Supplier實例便可,so,咱們來看registerBean僅僅須要一個基於構造器調用的supplier實例,沒有其餘。github

理由其實很簡單,由於這並非Java裏的Lambda表達式,而是一個Kotlin函數。它實際上調用了一個由Kotlin實現的registerBean重載函數。在咱們的 Kotlin 擴展中,Kotlin擴展函數是基於一個元數據模型(T::class.java)
一個反射模型(BeanDefinitionCustomizer)進行設計的。當咱們來問一個Kotlin函數,你會返回什麼,它預先已經知道了,而此時Java 8 lambda表達式是沒法進行返回類型檢查的。基於此,咱們能夠很好去使用這個特性,僅須要一個supplier實例(注:無須使用T.class進行類型限定)就能夠知道所獲得的組件類型。即經過supplier 實例建立的bean的類型。編程

咱們能夠經過Kotlin中一些其餘函數API來提升開發體驗。下面的這個版本基本上只是使用了一點語法糖,有一點點的語法差別。這是另一種應用Kotlin語言特性來實現目標API的方式。這不是一個正式的Gradle風格,只是有一點DSL(領域專用語言)的風格。感受有一點像Gradle構建工具 ,Kotlin風格的Gradle構建工具。後端

so,這裏經過一種不同的風格來表達Generic(這裏指GenericApplicationContext),但這只是Kotlin語言的一種變體,咱們這裏用它來實現咱們的目的,沒有什麼特別的。微信

2. 性能調優與GraalVM

圖片描述
ok,讓咱們開始轉向另外一個重要的話題。對於Spring Framework 5,咱們一直致力於不斷調整並提升它的性能架構

固然咱們也在努力改善提升開箱即用的性能。雖然和咱們的目標相關,但努力的方向有點不同。咱們試圖在代碼庫中避免一些性能不好的東西來減小沒必要要的開銷。同時咱們也有嘗試爲你提供帶hook的設施,一種你能夠用來調整性能的機制。若是你知道的話,你就能夠根據它作出最具體的假設,這些假設沒法經過通用的框架代碼來實現。
圖片描述
在5.2中最明顯的例子就是註解處理。最初,在5.1版本中,咱們主要經過對現有代碼進行修訂,但在5.2中咱們選擇徹底從新實現。遺憾的是,在java中註解的處理是一個至關複雜的事情。若是你以前有作過這樣的事情,你也許就知道個人意思,這也是框架平常所作的絕大多數事情。在一個應用程序的代碼中,你幾乎不須要去寫如何查找一個註解。也就是說你只須要聲明註解,框架會負責對它們進行正確的查找。這個過程其實很是複雜,效率又很低。主要緣由在於它們是由Java來實現的。咱們爲了作到最好,在這個方面咱們儘可能避免反射,避免經過代理。然而不幸的是,註解實例是經過Java代理實現的,主要也是基於JDK自身。所以,咱們竭盡所能,從開始就避免使用反射。app

關於AnnotationUtils和AnnotatedElementUtils 這兩個API基本上是相同的,你在使用SpringBoot的過程當中天然會使用到它們(咱們會使用註解,那就會使用到這些工具類)。你不多會親自去使用它們,可是基本上它們對你來說是透明的。(也就是你看不到,你只須要關心使用什麼註解,不須要關心註解背後的實現)若是你使用了其餘的Spring項目例如Spring Integration,Spring Batch,那你就能夠從這個透明特性裏面獲得很明顯的好處。框架

在Spring 5.2中有一個名爲MergedAnnotations的API,它對Spring聲明過的註解層次結構的內省很是有用。你可能感受到Spring的註解模型很複雜了。這裏有個元數據註解模型,你能夠在此之上覆寫它的屬性。你不須要作很複雜的事情,你能夠很輕易地經過這些選項獲取到。所以咱們引入了一種全新的API,它可使全部的內部檢查變得很是簡單直接,而且十分高效。

咱們的精力更多放在了對於組件中可存在註解和不可存在註解的註冊上面。你能夠經過編程規範來告訴容器某些註解類型只能存在於特定的組件類裏面。換句話講,在這種特定的組件類和特定的組件包中,根本就不可能有這些註解類型的使用。咱們不須要在這些地方去查找這些註解。在這些地方,你根本就找不到它們的。

這些假設咱們很難在程序中本身經過代碼來實現。正常狀況下,註解能夠被應用於任何地方,這是一個常識。可能因爲一些規定,你須要在你或團隊的代碼庫中服從一些約定,即特定的地方只能用一些特定的註解類型。若是你告訴咱們這些約定,咱們就能夠在運行代碼時減小這些註解產生的性能開銷。對此咱們已經在5.2中作了大量的工做,也就是經過這些信息在註解查找的時候儘可能跳過它。以此來整合出一個Java 標準的索引排列。

咱們在啓動的時候並無索引,咱們只有這些類文件。在運行時類索引並不指向類文件(指向的是JVM裏面的class字節碼)。咱們只能經過兩種途徑對註解進行內省。若是咱們在構建時想要獲取額外信息的話,就能夠經過像Jandex的索引或者是一個自定義的索引排列同樣來達到目的(經過索引來 存儲一些關鍵信息)。就好像你在其餘的基礎架構中所使用的索引同樣。在啓動時若是經過加載這樣的一個索引來提取信息,這個信息多是Spring ApplicationContext相關的內容,經過這個索引咱們就能立馬獲取到這個信息。這些在咱們的Bootstrap代碼中都有提供配置可進行調整。咱們也會在SpringBoot中會對引用排列進行從新評估,尤爲是這個東西它是否是已經能夠被SpringBoot自動使用。若是在使用時,發現了一個Jandex索引,SpringBoot會自動識別評估並應用它。

關於這塊,接下來的路還很長,但在七月份咱們會將咱們這些想法放到SpringBoot中。最重要的是若是你使用了這些功能(索引排列支持),那麼它將會是你整個架構的一個熱點(很明顯會大量的用到,由於解決了不少痛點)。同時,你能夠對這些可用功能進行調整以免沒必要要的開銷。
圖片描述
今天咱們另外一個主題則是GraalVM,它是最近比較火的一個話題。Spring框架對它的支持已經有一段時間了(即將Spring Boot Application封裝成一個GraalVM Native Image在上面運行),如今這部分仍然處於實驗階段。目前,咱們在github上已經簡單建立了一個基於GraalVM 19GA版本的wiki。經過它,人們能夠進行有針對性的討論,今年晚些時候,咱們也會對其進行專門的討論。
圖片描述
GraalVM Native Image 它究竟是什麼東西?它是一個很特別的部署架構。它並非咱們常見的JVM,二者徹底不同。它沒有任何動態類加載,也沒有任何動態內省。很直接的講,它並無你想的那麼不同凡響。GraalVM一樣支持反射。你只須要給Native Image工具提供配置文件,這樣它就能預先將正確的信息內置進Native Image。因此,在運行時你不能作任何動態反射,可是你能夠將你須要用到反射的地方進行提早配置。在這個基於GraalVM的定製版的Spring Framework application中咱們對prototypes 進行了實驗,獲得了比索引排列更好的效果。

拿咱們以前很熟悉的函數式Bean Registration(前面ppt中的例子)。經過內聯的Supplier註冊Bean的過程能夠很天然地在GraalVM上運行,你不須要去作什麼。在這裏咱們須要討論的是在GraalVM中,基於註解的組件模型須要進行一些額外的工做,你須要提早告訴GraalVM中的Native Image,你所要操做的組件類型以及內省。

咱們當下的目標基本仍是爲GraalVM作準備,咱們已經避免了一些沒必要要的反射點,同時也重製了一些代碼,例如能夠自動跳過那些沒用的以及對GraalVM沒有任何意義的工做,以提高開箱即用的體驗。它們其實已經在5.1中出現了很多,在5.2中尤甚。

附帶說明(wiki文檔)裏面也說起如何使用GraalVM 19早期採用版本中的Native Image工具。在咱們Spring Framework 5.3下一次迭代中,主要目標在於提高開箱即用的性能體驗。經過整合寫開箱即用的配置和構建工具,
你能夠很輕易的構建一個用於部署的基於Spring的Application GraalVM Native Image。目前而言,任重道遠。毋庸置疑,咱們如今也不清楚這個工具將來會是怎樣的,但咱們已經和Oracle團隊在GraalVM上緊密合做了至關長的一段時間。自從基於Spring 的 Native Image能夠在GraalVM上進行部署,二者結合的優勢也已經體現出來了。爲了能夠在GraalVM上運行,咱們已經作了至關一些改進。在對 GraalVM 19 使用時咱們給出了反饋意見,以後咱們會提供更多的反饋,以指望這些反饋會體如今GraalVM 20上。

從前面所講的這兩種方式來看,咱們能夠發現具備開箱即用功能的索引排列在當下多是更優的選擇。這個是咱們當前維護的wiki頁面。這基本上也是目前現有的狀態,我也在wiki上列出了咱們的一些前進方向。

固然,咱們選擇將基於Spring的應用程序經過Native Image的方式進行部署的主要理由,就是Native Image基於一種徹底不一樣的內存消耗模型以及更加快速的啓動方式。經過使用Native Image,咱們能獲取大量好處,同時也得進行一些取捨。你會犧牲一些性能,即在構建的的時候,你須要在生成Native Image上花不少時間。對我來講,這不是個簡單的決定。若是你真的須要性能上的調優,從個人觀點而言,你能夠選擇咱們所提供的首選方案(即索引排列)。
圖片描述
接下來有一些建議。若是你爲了性能提高去選擇優化特定的應用或者架構組織,你就必須在你的代碼或你的組件模型結構上作出必定的妥協。在此咱們提供了一系列的透明特性來讓大家得到好處,同時將它們作的儘量透明。固然你須要去找到你所要用到的配置選項進行配置。例如,若是你使用代碼的形式去註冊,這樣會顯得很臃腫。但若是你能明確聲明哪些組件你不須要,那麼在啓動的過程當中就能減小這些組件類的加載,以此來提升性能。這須要你進行不少的微調。相應的在Spring 5.2中,咱們提供了一些特定的設施。例如一些配置類,你能夠選擇將proxyBeanMethods設定爲false來避免在運行時建立CGLIB的子類。若是你的配置類不會出現一個Bean方法調用另外一個Bean方法,不然致使它們會被重定向到容器(會經過代理類進行調用)。若是你的Bean彼此獨立,不會互相調用,那麼你能夠在一個特定的配置類裏面設置proxyBeanMethods爲false來避免在運行時生成代理子類。咱們已經將上面的功能整合進了Spring Framework5.2和Spring Boot 2中。

另外一個有點奇怪的建議就是,我想我更喜歡基於接口的代理。每一個人都習慣使用代理來映射到目標類,也就意味着在運行時會建立CGLIB的代理子類。可是實際上基於良好的舊的接口代理,在啓動時建立的效率更高,而且它們能夠在GraalVM上作到開箱即用。Spring老是在基於接口的代理和基於類的代理之間進行默認使用選擇(對Spring Boot一樣如此)。你可使用任何一種AOP的對應實現,你也能夠明確的指出你想要使用的代理類型。若是你的組件模型結構是使用接口代理來實現的,那麼這將會對性能產生一個極大的提高。這對轉向GraalVM 有特別的意義,由於在這裏咱們不須要考慮CGLIB的配置。

相關文章
相關標籤/搜索