SpringBoot快速構(gòu)建微服務(wù)體系_第1頁(yè)
SpringBoot快速構(gòu)建微服務(wù)體系_第2頁(yè)
SpringBoot快速構(gòu)建微服務(wù)體系_第3頁(yè)
SpringBoot快速構(gòu)建微服務(wù)體系_第4頁(yè)
SpringBoot快速構(gòu)建微服務(wù)體系_第5頁(yè)
已閱讀5頁(yè),還剩172頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

SpringBoot快速構(gòu)建微服務(wù)體系目錄\h第1章了解微服務(wù)\h1.1什么是微服務(wù)\h1.2微服務(wù)因何而生\h1.3微服務(wù)會(huì)帶來(lái)哪些好處\h1.3.1獨(dú)立,獨(dú)立,還是獨(dú)立\h1.3.2多語(yǔ)言生態(tài)\h1.4微服務(wù)會(huì)帶來(lái)哪些挑戰(zhàn)\h1.5本章小結(jié)\h第2章飲水思源:回顧與探索Spring框架的本質(zhì)\h2.1Spring框架的起源\h2.2SpringIoC其實(shí)很簡(jiǎn)單\h2.3了解一點(diǎn)兒JavaConfig\h2.3.1那些高曝光率的Annotation\h2.4本章小結(jié)\h第3章SpringBoot的工作機(jī)制\h3.1SpringBoot初體驗(yàn)\h3.2@SpringBootApplication背后的秘密\h3.2.1@Configuration創(chuàng)世紀(jì)\h3.2.2@EnableAutoConfiguration的功效\h3.2.3可有可無(wú)的@ComponentScan\h3.3SpringApplication:SpringBoot程序啟動(dòng)的一站式解決方案\h3.3.1深入探索SpringApplication執(zhí)行流程\h3.3.2SpringApplicationRunListener\h3.3.3ApplicationListener\h3.3.4ApplicationContextInitializer\h3.3.5CommandLineRunner\h3.4再談自動(dòng)配置\h3.4.1基于條件的自動(dòng)配置\h3.4.2調(diào)整自動(dòng)配置的順序\h3.5本章小結(jié)\h第4章了解紛雜的spring-boot-starter\h4.1應(yīng)用日志和spring-boot-starter-logging\h4.2快速Web應(yīng)用開(kāi)發(fā)與spring-boot-starter-web\h4.2.1項(xiàng)目結(jié)構(gòu)層面的約定\h4.2.2SpringMVC框架層面的約定和定制\h4.2.3嵌入式Web容器層面的約定和定制\h4.3數(shù)據(jù)訪問(wèn)與spring-boot-starter-jdbc\h4.3.1SpringBoot應(yīng)用的數(shù)據(jù)庫(kù)版本化管理\h4.4spring-boot-starter-aop及其使用場(chǎng)景說(shuō)明\h4.4.1spring-boot-starter-aop在構(gòu)建spring-boot-starter-metrics自定義模塊中的應(yīng)用\h4.5應(yīng)用安全與spring-boot-starter-security\h4.5.1了解SpringSecurity基本設(shè)計(jì)\h4.5.2進(jìn)一步定制spring-boot-starter-security\h4.6應(yīng)用監(jiān)控與spring-boot-starter-actuator\h4.6.1自定義應(yīng)用的健康狀態(tài)檢查\h4.6.2開(kāi)放的endpoints才真正“有用”\h4.6.3用還是不用,這是個(gè)問(wèn)題\h4.7本章小結(jié)\h第5章SpringBoot微服務(wù)實(shí)踐探索\h5.1使用SpringBoot構(gòu)建微服務(wù)\h5.1.1創(chuàng)建基于Dubbo框架的SpringBoot微服務(wù)\h5.1.2使用SpringBoot快速構(gòu)建WebAPI\h5.1.3使用SpringBoot構(gòu)建其他形式的微服務(wù)\h5.2SpringBoot微服務(wù)的發(fā)布與部署\h5.2.1spring-boot-starter的發(fā)布與部署方式\h5.2.2基于RPM的發(fā)布與部署方式\h5.2.3基于Docker的發(fā)布與部署方式\h5.3SpringBoot微服務(wù)的注冊(cè)與發(fā)現(xiàn)\h5.4SpringBoot微服務(wù)的監(jiān)控與運(yùn)維\h5.4.1推還是拉,這一直是個(gè)問(wèn)題\h5.4.2從局部性觸發(fā)式報(bào)警到系統(tǒng)性智能化報(bào)警\h5.5SpringBoot微服務(wù)的安全與防護(hù)\h5.6SpringBoot微服務(wù)體系的脊梁:發(fā)布與部署平臺(tái)\h5.7本章小結(jié)\h第6章SpringBoot與Scala\h6.1使用Maven構(gòu)建和發(fā)布基于SpringBoot的Scala應(yīng)用\h6.1.1進(jìn)一步簡(jiǎn)化基于Maven的Scala項(xiàng)目創(chuàng)建\h6.1.2進(jìn)一步簡(jiǎn)化基于Scala的WebAPI開(kāi)發(fā)\h6.2使用SBT構(gòu)建和發(fā)布基于SpringBoot的Scala應(yīng)用\h6.2.1探索基于SBT的SpringBoot應(yīng)用開(kāi)發(fā)模式\h6.2.2探索基于SBT的SpringBoot應(yīng)用發(fā)布策略\h6.3本章小結(jié)\h第7章SpringBoot總結(jié)與展望第1章了解微服務(wù)SpringBoot是一個(gè)可使用Java構(gòu)建微服務(wù)的微框架,所以在了解SpringBoot之前,我們需要先了解什么是微服務(wù)。1.1什么是微服務(wù)微服務(wù)(Microservice)雖然是當(dāng)下剛興起的比較流行的新名詞,但本質(zhì)上來(lái)說(shuō),微服務(wù)并非什么新的概念。實(shí)際上,很多SOA實(shí)施成熟度比較好的企業(yè),已經(jīng)在使用和實(shí)施微服務(wù)了。只不過(guò),它們只是在悶聲發(fā)大財(cái),并不介意是否有一個(gè)比較時(shí)髦的名詞來(lái)明確表述SOA的這個(gè)發(fā)展演化趨勢(shì)罷了。微服務(wù)其實(shí)就是服務(wù)化思路的一種最佳實(shí)踐方向,遵循SOA的思路,各個(gè)企業(yè)在服務(wù)化治理的道路上走的時(shí)間長(zhǎng)了,踩的坑多了,整個(gè)軟件交付鏈路上各個(gè)環(huán)節(jié)的基礎(chǔ)設(shè)施逐漸成熟了,微服務(wù)自然而然就誕生了。當(dāng)然,之所以叫微服務(wù),是與之前的服務(wù)化思路和實(shí)踐相比較而來(lái)的。早些年的服務(wù)實(shí)現(xiàn)和實(shí)施思路是將很多功能從開(kāi)發(fā)到交付都打包成一個(gè)很大的服務(wù)單元(一般稱為Monolith),而微服務(wù)實(shí)現(xiàn)和實(shí)施思路則更強(qiáng)調(diào)功能趨向單一,服務(wù)單元小型化和微型化。如果用“茶壺煮餃子”來(lái)打比方的話,原來(lái)我們是在一個(gè)茶壺里煮很多個(gè)餃子,現(xiàn)在(微服務(wù)化之后)則基本上是在一個(gè)茶壺煮一個(gè)餃子,而這些餃子就是服務(wù)的功能,茶壺則是將這些服務(wù)功能打包交付的服務(wù)單元,如圖1-1所示。圖1-1論茶壺里煮“餃子”的不同形式所以,從思路和理念上來(lái)講,微服務(wù)就是要倡導(dǎo)大家盡量將功能進(jìn)行拆分,將服務(wù)粒度做小,使之可以獨(dú)立承擔(dān)對(duì)外服務(wù)的職責(zé),沿著這個(gè)思路開(kāi)發(fā)和交付的軟件服務(wù)實(shí)體就叫作“微服務(wù)”,而圍繞著這個(gè)思路和理念構(gòu)建的一系列基礎(chǔ)設(shè)施和指導(dǎo)思想,筆者將它稱為“微服務(wù)體系”。1.2微服務(wù)因何而生微服務(wù)的概念我們應(yīng)該大體了解了,那么微服務(wù)又是怎么來(lái)的?原來(lái)將很多功能打包為一個(gè)很大的服務(wù)單元進(jìn)行交付的做法不能滿足需求嗎?實(shí)際上,并非原來(lái)“大一統(tǒng)”(Monolith)的服務(wù)化實(shí)踐不能滿足要求,也不是不好,只是,它有自己存在的合理場(chǎng)景。對(duì)于Monolith服務(wù)來(lái)說(shuō),如果團(tuán)隊(duì)不大,軟件復(fù)雜度不高,那么,使用Monolith的形式進(jìn)行服務(wù)化治理是比較合適的,而且,這種方式對(duì)運(yùn)維和各種基礎(chǔ)設(shè)施的要求也不高。但是,隨著軟件系統(tǒng)的復(fù)雜度持續(xù)飆升,軟件交付的效率要求更高,投入的人力以及各項(xiàng)資源越來(lái)越多,基于Monolith的服務(wù)化思路就開(kāi)始“捉襟見(jiàn)肘”。在開(kāi)發(fā)階段,如果我們遵循Monolith的服務(wù)化理念,通常會(huì)將所有功能的實(shí)現(xiàn)都統(tǒng)一歸到一個(gè)開(kāi)發(fā)項(xiàng)目下,但隨著功能的膨脹,這些功能一定會(huì)分發(fā)給不同的研發(fā)人員進(jìn)行開(kāi)發(fā),造成的后果就是,大家在提交代碼的時(shí)候頻繁沖突并需要解決這些沖突,單一的開(kāi)發(fā)項(xiàng)目成為了開(kāi)發(fā)期間所有人的工作瓶頸。為了減輕這種苦惱,我們自然會(huì)將項(xiàng)目按照要開(kāi)發(fā)的功能拆分為不同的項(xiàng)目,從而負(fù)責(zé)不同功能的研發(fā)人員就可以在自己的代碼項(xiàng)目上進(jìn)行開(kāi)發(fā),從而解決了大家無(wú)法在開(kāi)發(fā)階段并行開(kāi)發(fā)的苦惱。到了軟件交付階段,如果我們遵循Monolith的服務(wù)化理念,那么,我們一定是將所有這些開(kāi)發(fā)階段并行開(kāi)發(fā)的項(xiàng)目集合到一起進(jìn)行交付,這就涉及服務(wù)化早期實(shí)踐中比較有名的“火車模型”,即交付的服務(wù)就像一輛火車,而這個(gè)服務(wù)相關(guān)的所有功能對(duì)應(yīng)的項(xiàng)目成果,就是要裝上火車車廂的一件件貨物,交付的列車只有等到所有項(xiàng)目都開(kāi)發(fā)測(cè)試完成后才可以裝車出發(fā),完成整個(gè)服務(wù)的交付。很顯然,只要有一個(gè)車廂沒(méi)有準(zhǔn)備好貨物(即功能項(xiàng)目未開(kāi)發(fā)測(cè)試完成),火車就不能發(fā)車,服務(wù)就不能交付,這大大降低了服務(wù)的交付效率。如果每個(gè)功能項(xiàng)目可以各自獨(dú)立交付,那么就不需要都等同一輛火車,各自出發(fā)就可以了。順著這個(gè)思路,自然而然地,大家逐漸各自獨(dú)立,每一個(gè)功能或者少數(shù)相近的功能作為單一項(xiàng)目開(kāi)發(fā)完成后將作為一個(gè)獨(dú)立的服務(wù)單元進(jìn)行交付,從而在服務(wù)交付階段,大家也能夠并行不悖,各自演化而不受影響。所以,隨著服務(wù)和系統(tǒng)的復(fù)雜度逐漸飆升,為了能夠在整個(gè)軟件的交付鏈路上高效擴(kuò)展,將獨(dú)立的功能和服務(wù)單元進(jìn)行拆分,從而形成一個(gè)一個(gè)的微服務(wù)是自然而然發(fā)生的事情。這就像打不同的戰(zhàn)役一樣,在雙方兵力不多、戰(zhàn)場(chǎng)復(fù)雜度不高的情況下,Monolith的統(tǒng)一指揮調(diào)度方式是合適的;而一旦要打大的戰(zhàn)役(類似于系統(tǒng)復(fù)雜度提升),雙方一定會(huì)投入大量的兵力(軟件研發(fā)團(tuán)隊(duì)的規(guī)模增長(zhǎng)),如果還是在狹小甚至固定的戰(zhàn)場(chǎng)上進(jìn)行廝殺,顯然施展不開(kāi)!所以,小戰(zhàn)役有小戰(zhàn)役的打法,大戰(zhàn)役有大戰(zhàn)役的戰(zhàn)法,而微服務(wù)實(shí)際上就是一種幫助擴(kuò)展組織能力、提升團(tuán)隊(duì)效率的應(yīng)對(duì)“大戰(zhàn)役”的方法,它幫助我們從軟件開(kāi)發(fā)到交付,進(jìn)而到團(tuán)隊(duì)和組織層面多方位進(jìn)行擴(kuò)展??偟膩?lái)說(shuō),一方面微服務(wù)可以幫助我們應(yīng)對(duì)飆升的系統(tǒng)復(fù)雜度;另一個(gè)方面,微服務(wù)可以幫助我們進(jìn)行更大范圍的擴(kuò)展,從開(kāi)發(fā)階段項(xiàng)目并行開(kāi)發(fā)的擴(kuò)展,到交付階段并行交付的擴(kuò)展,再到相應(yīng)的組織結(jié)構(gòu)和組織能力的擴(kuò)展,皆因微服務(wù)而受惠。1.3微服務(wù)會(huì)帶來(lái)哪些好處顯然,隨著系統(tǒng)復(fù)雜度的提升,以及對(duì)系統(tǒng)擴(kuò)展性的要求越來(lái)越高,微服務(wù)化是一個(gè)很好的方向,但除此之外,微服務(wù)還會(huì)給我們帶來(lái)哪些好處?1.3.1獨(dú)立,獨(dú)立,還是獨(dú)立我們說(shuō)微服務(wù)打響的是各自的獨(dú)立戰(zhàn)爭(zhēng),所以,每一個(gè)微服務(wù)都是一個(gè)小王國(guó),這些微服務(wù)跳出了“大一統(tǒng)”(Monolith)王國(guó)的統(tǒng)治,開(kāi)始從各個(gè)層面打造自己的獨(dú)立能力,從而保障自己的小王國(guó)可以持續(xù)穩(wěn)固的運(yùn)轉(zhuǎn)。首先,在開(kāi)發(fā)層面,每個(gè)微服務(wù)基本上都是各自獨(dú)立的項(xiàng)目(project),而對(duì)應(yīng)各自獨(dú)立項(xiàng)目的研發(fā)團(tuán)隊(duì)基本上也是獨(dú)立對(duì)應(yīng),這樣的結(jié)構(gòu)保證了微服務(wù)的并行研發(fā),并且各自快速迭代,不會(huì)因?yàn)樗醒邪l(fā)都投入一個(gè)近乎單點(diǎn)的項(xiàng)目,從而造成開(kāi)發(fā)階段的瓶頸。開(kāi)發(fā)階段的獨(dú)立,保證了微服務(wù)的研發(fā)可以高效進(jìn)行。服務(wù)開(kāi)發(fā)期間的形態(tài),跟服務(wù)交付期間的形態(tài)原則上是不需要完全高度統(tǒng)一的,即使我們?cè)陂_(kāi)發(fā)的時(shí)候都是各自進(jìn)行,但交付的時(shí)候還是可以一起交付,不過(guò)這不是微服務(wù)的做法。在微服務(wù)治理體系下,各個(gè)微服務(wù)交付期間也是各自獨(dú)立交付的,從而使得每個(gè)微服務(wù)從開(kāi)發(fā)到交付整條鏈路上都是獨(dú)立進(jìn)行,這大大加快了微服務(wù)的迭代和交付效率。服務(wù)交付之后需要部署運(yùn)行,對(duì)微服務(wù)來(lái)說(shuō),它們運(yùn)行期間也是各自獨(dú)立的。微服務(wù)獨(dú)立運(yùn)行可以帶來(lái)兩個(gè)比較明顯的好處,第一個(gè)就是可擴(kuò)展性。我們可以快速地添加服務(wù)集群的實(shí)例,提升整個(gè)微服務(wù)集群的服務(wù)能力,而在傳統(tǒng)Monolith模式下,為了能夠提升服務(wù)能力,很多時(shí)候必須強(qiáng)化和擴(kuò)展單一結(jié)點(diǎn)的服務(wù)能力來(lái)達(dá)成。如果單結(jié)點(diǎn)服務(wù)能力已經(jīng)擴(kuò)展到了極限,再尋求擴(kuò)展的話,就得從軟件到硬件整體進(jìn)行重構(gòu)。軟件行業(yè)有句話:“Threadsdon'tscale,Processesdo!”,很明確地道出了原來(lái)Monolith服務(wù)與微服務(wù)在擴(kuò)展(Scale)層面的差異。對(duì)于Java開(kāi)發(fā)者來(lái)說(shuō),早些年(當(dāng)然現(xiàn)在也依然存在),我們遵循JavaEE規(guī)范開(kāi)發(fā)的Web應(yīng)用,都需要以WAR包的形式部署到TOMCAT、Jetty、RESIN等Web容器中運(yùn)行,即使每個(gè)WAR包提供的都是獨(dú)立的微服務(wù),但因?yàn)樗鼈兌际墙y(tǒng)一部署運(yùn)行在一個(gè)Web容器中,所以擴(kuò)展能力受限于Web容器作為一個(gè)進(jìn)程(process)的現(xiàn)狀。無(wú)論如何調(diào)整Web容器內(nèi)部實(shí)現(xiàn)的線程(thread)設(shè)置,還是會(huì)受限于Web容器整體的擴(kuò)展能力。所以,現(xiàn)在很多情況下,大家都是一個(gè)TOMCAT只部署一個(gè)WAR,然后通過(guò)復(fù)制和擴(kuò)展多個(gè)TOMCAT實(shí)例來(lái)擴(kuò)展整個(gè)應(yīng)用服務(wù)集群。當(dāng)然,說(shuō)到在TOMCAT實(shí)例中只部署一個(gè)WAR包這樣的做法,實(shí)際上不單單只是因?yàn)閿U(kuò)展的因素,還涉及微服務(wù)運(yùn)行期間給我們帶來(lái)的第二個(gè)好處,即隔離性。隔離性實(shí)際上是可擴(kuò)展性的基礎(chǔ),當(dāng)我們將每個(gè)微服務(wù)都隔離為獨(dú)立的運(yùn)行單元之后,任何一個(gè)或者多個(gè)微服務(wù)的失敗都將只影響自己或者少量其他微服務(wù),而不會(huì)大面積地波及整個(gè)服務(wù)運(yùn)行體系。在架構(gòu)設(shè)計(jì)上有一種實(shí)踐模式,即隔板模式(BulkheadPattern),這種架構(gòu)設(shè)計(jì)模式的首要目的就是為了隔離系統(tǒng)中的各個(gè)功能單元和實(shí)體,使得系統(tǒng)不會(huì)因?yàn)橐粋€(gè)單元或者服務(wù)的失敗而導(dǎo)致整體失敗。這種思路在造船行業(yè)、兵工行業(yè)都有類似的應(yīng)用場(chǎng)景?,F(xiàn)在任何大型船舶在設(shè)計(jì)上都會(huì)有隔艙,目的就是即使有少量進(jìn)水,也可以只將進(jìn)水部位隔離在小范圍,不會(huì)擴(kuò)散而導(dǎo)致船舶大面積進(jìn)水,從而沉沒(méi)。當(dāng)年泰坦尼克號(hào)雖然沉了,但不意味著他們沒(méi)有做隔艙設(shè)計(jì),只能說(shuō),傷害度已經(jīng)遠(yuǎn)遠(yuǎn)超出隔艙可以提供的基礎(chǔ)保障范圍。在坦克的設(shè)計(jì)上,現(xiàn)在一般也會(huì)將彈藥艙和乘員艙隔離,從而可以保障當(dāng)坦克受創(chuàng)之后,將傷害盡量限定在指定區(qū)域,盡量減少對(duì)車乘成員的傷害。前面我們提到,現(xiàn)在大家基本上弱化了JavaEE的Web容器早期采用的“一個(gè)Web容器部署多個(gè)WAR包”的做法,轉(zhuǎn)而使用“一個(gè)Web容器只部署一個(gè)WAR包”的做法,這實(shí)際上正是綜合考慮了Web容器的設(shè)計(jì)和實(shí)現(xiàn)現(xiàn)狀與真實(shí)需求之后做出的合理實(shí)踐選擇。這些Web容器內(nèi)部大多通過(guò)類加載器(Classloader)以及線程來(lái)實(shí)現(xiàn)一定程度上的依賴和功能隔離,但這些機(jī)制從基因上決定了這些做法不是最好的隔離手段。而進(jìn)程(Process)擁有天然的隔離特性,所以,一個(gè)WAR包只部署運(yùn)行在一個(gè)Web容器進(jìn)程中才是最好的隔離方式?,F(xiàn)在回想一下,好像自從各個(gè)微服務(wù)打響?yīng)毩?zhàn)爭(zhēng)并且獨(dú)立之后,無(wú)論從哪個(gè)層面來(lái)看,各自“活”得都挺好。1.3.2多語(yǔ)言生態(tài)微服務(wù)獨(dú)立之后,給了對(duì)應(yīng)的團(tuán)隊(duì)和組織快速迭代和交付的能力,同時(shí),也給團(tuán)隊(duì)和組織帶來(lái)了更多的靈活性,實(shí)際上,對(duì)應(yīng)交付不同微服務(wù)的團(tuán)隊(duì)或者組織來(lái)說(shuō),現(xiàn)在可以基于不同的計(jì)算機(jī)語(yǔ)言生態(tài)構(gòu)建這些微服務(wù),如圖1-2所示。微服務(wù)的提供者既可以使用Java或者Go等靜態(tài)語(yǔ)言完成微服務(wù)的開(kāi)發(fā)和交付,也可以使用Python或者Ruby等動(dòng)態(tài)語(yǔ)言完成微服務(wù)的開(kāi)發(fā)和交付,對(duì)于團(tuán)隊(duì)內(nèi)部擁有繁榮且有差異的語(yǔ)言文化來(lái)說(shuō),多語(yǔ)言生態(tài)下的微服務(wù)開(kāi)發(fā)和交付將可以最大化的發(fā)揮團(tuán)隊(duì)和組織內(nèi)部各成員的優(yōu)勢(shì)。當(dāng)然,對(duì)于多語(yǔ)言生態(tài)下的微服務(wù)研發(fā)來(lái)說(shuō),有一點(diǎn)需要注意:為了讓服務(wù)的訪問(wèn)者可以用統(tǒng)一的接口訪問(wèn)所有這些用不同語(yǔ)言開(kāi)發(fā)和交互的微服務(wù),應(yīng)該盡量統(tǒng)一微服務(wù)的服務(wù)接口和協(xié)議。在微服務(wù)的生態(tài)下,互通性應(yīng)該是需要重點(diǎn)關(guān)注的因素,沒(méi)有互通,不但服務(wù)的訪問(wèn)者和用戶無(wú)法很好地使用這些微服務(wù),微服務(wù)和微服務(wù)之間也無(wú)法相互信賴和互助,這將大大損耗微服務(wù)研發(fā)體系帶來(lái)的諸多好處,而多語(yǔ)言生態(tài)也會(huì)變成一種障礙和負(fù)累,而不是益處。記得時(shí)任黑貓宅急便社長(zhǎng)的小倉(cāng)昌男在其所著的《黑貓宅急便的經(jīng)營(yíng)學(xué)》中提到一個(gè)故事,日本國(guó)鐵曾經(jīng)采用不同于國(guó)際標(biāo)準(zhǔn)的集裝箱和鐵路規(guī)格,然后發(fā)現(xiàn)貨物的運(yùn)輸效率很低,經(jīng)過(guò)考察發(fā)現(xiàn),原來(lái)是貨物從國(guó)際標(biāo)準(zhǔn)集裝箱卸載之后,在通過(guò)日本國(guó)鐵運(yùn)輸之前,需要先拆箱,重新裝入日本國(guó)鐵規(guī)格的集裝箱,然后裝載到日本國(guó)鐵上進(jìn)行運(yùn)輸。但是,如果日本國(guó)鐵采用國(guó)際標(biāo)準(zhǔn)的集裝箱規(guī)格,那么貨物集裝箱從遠(yuǎn)洋輪船上卸載之后就可以直接裝上國(guó)鐵,這將大大加快運(yùn)輸效率(日本,國(guó)鐵改革后也證明確實(shí)如此)。日本國(guó)鐵在前期采用私有方案時(shí),只關(guān)注了自己的利益和效率,舍棄了互通,也帶來(lái)了效率的低下。所以,在開(kāi)發(fā)和交付微服務(wù)的時(shí)候,尤其是在多語(yǔ)言生態(tài)下開(kāi)發(fā)和交付微服務(wù),我們從一開(kāi)始就要將互通性作為首要考慮因素,從而不會(huì)因?yàn)閳?zhí)迷于某些服務(wù)或者系統(tǒng)的單點(diǎn)效率而失去了整個(gè)微服務(wù)體系的整體效率。圖1-2多語(yǔ)言的微服務(wù)生態(tài)1.4微服務(wù)會(huì)帶來(lái)哪些挑戰(zhàn)微服務(wù)給我們帶來(lái)的并非只有好處,還有相應(yīng)的一些挑戰(zhàn)。服務(wù)“微”化之后,一個(gè)顯著的特點(diǎn)就是服務(wù)的數(shù)量增多了。如果將軟件開(kāi)發(fā)和交付也作為一種生產(chǎn)模式看待,那么數(shù)量眾多的微服務(wù)實(shí)際上就類似于傳統(tǒng)生產(chǎn)線上的產(chǎn)品,而在傳統(tǒng)生產(chǎn)模型下,為了能夠高效地生產(chǎn)大量產(chǎn)品,通常采用的就是標(biāo)準(zhǔn)化生產(chǎn)。比如在汽車產(chǎn)業(yè),在福特T型車沒(méi)有出來(lái)之前,大多汽車企業(yè)的生產(chǎn)效率都不高,而福特在引入標(biāo)準(zhǔn)化生產(chǎn)線之后,福特T型車得以大量生產(chǎn)并以低成本優(yōu)勢(shì)快速普及。在其他行業(yè)也是同樣的道理,個(gè)性化生產(chǎn)雖然會(huì)深得個(gè)別用戶的喜歡,但生產(chǎn)成本通常也會(huì)很高,生產(chǎn)效率因?yàn)槭芟抻趥€(gè)性化需求,也無(wú)法從“熟能生巧”中獲益,所以,最終用戶需要為生產(chǎn)成本和效率付出更多的溢價(jià)才能獲得最終產(chǎn)品。而相對(duì)于個(gè)性化生產(chǎn)來(lái)說(shuō),標(biāo)準(zhǔn)化生產(chǎn)走的是另一條路,通過(guò)生產(chǎn)標(biāo)準(zhǔn)產(chǎn)品,使得整條生產(chǎn)鏈路可重復(fù),從而提升了生產(chǎn)效率,可以為更廣層面的用戶提供大量“物美價(jià)廉”的標(biāo)準(zhǔn)產(chǎn)品。微服務(wù)的研發(fā)和交付其實(shí)就類似于產(chǎn)品的生產(chǎn)鏈路,而數(shù)量大這一特點(diǎn)則決定了,我們無(wú)法通過(guò)個(gè)性化的生產(chǎn)模式來(lái)支撐整個(gè)微服務(wù)的交付鏈路和研發(fā)體系,雖然微服務(wù)化之后,我們可以投入相應(yīng)的人力和團(tuán)隊(duì)對(duì)應(yīng)各個(gè)微服務(wù)的開(kāi)發(fā)和交付,可擴(kuò)展性上絕對(duì)沒(méi)有問(wèn)題,但這不意味著現(xiàn)實(shí)情況下我們就能這樣做,因?yàn)檫@些都涉及人力和資源成本,而這往往是受限的。所以,使用標(biāo)準(zhǔn)化的思路來(lái)開(kāi)發(fā)和交付微服務(wù)就變成了自然而然的選擇:·通過(guò)標(biāo)準(zhǔn)化,我們可以重復(fù)使用開(kāi)發(fā)階段打造的一系列環(huán)境和工具支持?!ねㄟ^(guò)標(biāo)準(zhǔn)化,我們可以復(fù)用支持整個(gè)微服務(wù)交付鏈路的各項(xiàng)基礎(chǔ)設(shè)施。·通過(guò)標(biāo)準(zhǔn)化,我們可以減少采購(gòu)差異導(dǎo)致的成本上升,同時(shí)更加高效地利用硬件資源?!ねㄟ^(guò)標(biāo)準(zhǔn)化,我們可以用標(biāo)準(zhǔn)的協(xié)議和格式來(lái)治理和維護(hù)數(shù)量龐大的微服務(wù)。如果你還對(duì)使用標(biāo)準(zhǔn)化的思路來(lái)構(gòu)建微服務(wù)體系存有疑惑,那么,不妨再結(jié)合微服務(wù)的多語(yǔ)言生態(tài)特性思考一番:·增加一種語(yǔ)言生態(tài)用于微服務(wù)的開(kāi)發(fā)和交付,我們是否要圍繞著這種語(yǔ)言生態(tài)和微服務(wù)的需求重新搭建一套研發(fā)/測(cè)試環(huán)境?·我們是否還要圍繞著這種語(yǔ)言生態(tài)打造一系列的工具來(lái)提升日常開(kāi)發(fā)的效率?·增加一種語(yǔ)言生態(tài),我們是不是還要圍繞這種語(yǔ)言生態(tài)搭建一套針對(duì)微服務(wù)的交付鏈路基礎(chǔ)設(shè)施?·增加一種語(yǔ)言生態(tài),我們是否還要圍繞它提供特定的硬件環(huán)境以及運(yùn)維支撐工具和平臺(tái)?多語(yǔ)言生態(tài)雖然靈活度高了,不同語(yǔ)種和思路的團(tuán)隊(duì)成員也能夠百花齊放了,但是不是也同樣帶來(lái)了以上一系列的成本?所以,很多事情你能做,并不意味著你一定要做。適度的收縮語(yǔ)言生態(tài)的選擇范圍,并圍繞主要的語(yǔ)言生態(tài)構(gòu)建一套標(biāo)準(zhǔn)化的微服務(wù)交付體系,或許是更為合理的做法。要實(shí)施高效可重復(fù)的標(biāo)準(zhǔn)化微服務(wù)生產(chǎn),我們需要有類似傳統(tǒng)行業(yè)生產(chǎn)線的基礎(chǔ)設(shè)施。否則,高效可重復(fù)的開(kāi)發(fā)和交付大量的微服務(wù)就無(wú)從談起,所以,完備的微服務(wù)研發(fā)和交付體系基礎(chǔ)設(shè)施建設(shè)就成為了實(shí)施微服務(wù)的終極挑戰(zhàn)。一個(gè)公司或者組織要很好地或者說(shuō)成熟地實(shí)施微服務(wù)化戰(zhàn)略,為交付鏈路提供完備支撐的基礎(chǔ)設(shè)施建設(shè)必不可少!1.5本章小結(jié)在帶領(lǐng)大家探索本書的主角SpringBoot微框架之前,本章首先為大家介紹了SpringBoot微框架服務(wù)的核心場(chǎng)景,即微服務(wù)。然后一起探索了微服務(wù)的概念以及由來(lái),并探討了微服務(wù)可以為我們帶來(lái)哪些好處,以及同時(shí)又為我們帶來(lái)哪些挑戰(zhàn)。總的來(lái)說(shuō),微服務(wù)化雖然是當(dāng)下流行的趨勢(shì),但并非任何場(chǎng)景都合適,我們還是要審慎地在“大一統(tǒng)”(Monolith)服務(wù)架構(gòu)和微服務(wù)架構(gòu)之間做出選擇,而一旦確定選擇了微服務(wù)化之路,那么,就應(yīng)該圍繞團(tuán)隊(duì)和組織的主要語(yǔ)言生態(tài)以及微服務(wù)方向積極探索高效的微服務(wù)開(kāi)發(fā)和交付模式。SpringBoot微框架實(shí)際上就是為Java語(yǔ)言生態(tài)而生的一種微服務(wù)最佳實(shí)踐,在第2章中我們將從回顧SpringBoot的起源開(kāi)始,逐步揭開(kāi)SpringBoot微框架的神秘面紗。第2章飲水思源:回顧與探索Spring框架的本質(zhì)SpringBoot框架的命名關(guān)鍵在“Boot”上,或許BootSpring更能說(shuō)明這個(gè)微框架設(shè)計(jì)的初衷,也就是快速啟動(dòng)一個(gè)Spring應(yīng)用!所以,自始至終,SpringBoot框架都是為了能夠幫助使用Spring框架的開(kāi)發(fā)者快速高效地構(gòu)建一個(gè)個(gè)基于Spring框架以及Spring生態(tài)體系的應(yīng)用解決方案。要深刻理解SpringBoot框架,首先我們需要深刻理解Spring框架,所以讓我們先來(lái)讀讀歷史吧!2.1Spring框架的起源雖然筆者在自己的上一本著作《Spring揭秘》中對(duì)Spring框架進(jìn)行了十分詳盡的介紹和剖析,但這里還是要再啰嗦幾句。Spring框架誕生于“黑暗”的EJB1的時(shí)代(如果你沒(méi)有聽(tīng)說(shuō)過(guò),恭喜你,說(shuō)明你還年輕),那是一個(gè)J2EE規(guī)范統(tǒng)治的時(shí)代,基于各種容器和J2EE規(guī)范的軟件解決方案是唯一的“正道”,沉重的研發(fā)模式和生態(tài)讓那個(gè)時(shí)代的開(kāi)發(fā)者痛苦不堪。隨著經(jīng)典巨著《ExpertOne-on-OneJ2EEDesignandDevelopment》的誕生,重規(guī)范時(shí)代終于迎來(lái)了一線曙光,該書的作者RodJohnson在書中闡述了輕量級(jí)框架的研發(fā)理念,對(duì)原有笨重的規(guī)范進(jìn)行了抨擊,并基于書中的理念推出了最初版的Spring框架,并延續(xù)至今已達(dá)10多年之久。Spring框架是構(gòu)建高效Java研發(fā)體系的一種最佳實(shí)踐,它通過(guò)一系列統(tǒng)一而簡(jiǎn)潔的設(shè)計(jì),為廣大Java開(kāi)發(fā)者開(kāi)拓了一條光明的Java應(yīng)用最佳實(shí)踐之路。大家熟知的SpringIoC與AOP自不必說(shuō),Spring更是對(duì)Java應(yīng)用開(kāi)發(fā)中常用的技術(shù)進(jìn)行了合理的設(shè)計(jì)和封裝,使得Java應(yīng)用開(kāi)發(fā)者可以避免昔日因API和系統(tǒng)設(shè)計(jì)不當(dāng)而易犯的錯(cuò)誤,又能夠高效地完成相應(yīng)問(wèn)題領(lǐng)域的研發(fā)工作,真可說(shuō)是Java開(kāi)發(fā)必備良器!當(dāng)然,因?yàn)檫@不是一本專門介紹Spring框架的書,所以,這里不會(huì)詳細(xì)展開(kāi)對(duì)Spring框架的細(xì)節(jié)回顧。不過(guò),一些核心的實(shí)踐以及與SpringBoot相關(guān)的概念,還是有必要說(shuō)在前的,比如SpringIoC!2.2SpringIoC其實(shí)很簡(jiǎn)單有部分Java開(kāi)發(fā)者對(duì)IoC(InversionOfControl)和DI(DependencyInjection)的概念有些混淆,認(rèn)為二者是對(duì)等的,實(shí)際上我在之前的著作中已經(jīng)說(shuō)過(guò)了,IoC其實(shí)有兩種方式,一種就是DI,而另一種是DL,即DependencyLookup(依賴查找),前者是當(dāng)前軟件實(shí)體被動(dòng)接受其依賴的其他組件被IoC容器注入,而后者則是當(dāng)前軟件實(shí)體主動(dòng)去某個(gè)服務(wù)注冊(cè)地查找其依賴的那些服務(wù),概念之間的關(guān)系如圖2-1所示可能更貼切些。圖2-1IoC相關(guān)概念示意圖我們通常提到的SpringIoC,實(shí)際上是指Spring框架提供的IoC容器實(shí)現(xiàn)(IoCContainer),而使用SpringIoC容器的一個(gè)典型代碼片段就是:publicclassApp{

publicstaticvoidmain(String[]args){

ApplicationContextcontext=newFileSystemXmlApplication-Context("...");

//...

MockServiceservice=context.getBean(MockService.class);

service.doSomething();

}

}

任何一個(gè)使用Spring框架構(gòu)建的獨(dú)立的Java應(yīng)用(StandaloneJavaApplication),通常都會(huì)存在一行類似于“context.getBean(..);”的代碼,實(shí)際上,這行代碼做的就是DL的工作,而構(gòu)建的任何一種IoC容器背后(比如BeanFactory或者ApplicationContext)發(fā)生的事情,則更多是DI的過(guò)程(也可能有部分DL的邏輯用于對(duì)接遺留系統(tǒng))。Spring的IoC容器中發(fā)生的事情其實(shí)也很簡(jiǎn)單,總結(jié)下來(lái)即兩個(gè)階段:(1)采摘和收集“咖啡豆”(bean)(2)研磨和烹飪咖啡哦,不對(duì),這是一本技術(shù)書,差點(diǎn)兒寫成咖啡文化雜志。那我們還是回過(guò)頭來(lái)繼續(xù)說(shuō)SpringIoC容器的依賴注入流程吧!SpringIoC容器的依賴注入工作可以分為兩個(gè)階段:階段一:收集和注冊(cè)第一個(gè)階段可以認(rèn)為是構(gòu)建和收集bean定義的階段,在這個(gè)階段中,我們可以通過(guò)XML或者Java代碼的方式定義一些bean,然后通過(guò)手動(dòng)組裝或者讓容器基于某些機(jī)制自動(dòng)掃描的形式,將這些bean定義收集到IoC容器中。假設(shè)我們以XML配置的形式來(lái)收集并注冊(cè)單一bean,一般形式如下:<beanid="mockService"class="..MockServiceImpl">

...

</bean>

如果嫌逐個(gè)收集bean定義麻煩,想批量地收集并注冊(cè)到IoC容器中,我們也可以通過(guò)XMLSchema形式的配置進(jìn)行批量掃描并采集和注冊(cè):<context:component-scanbase-package="com.keevol">

注意基于JavaConfig形式的收集和注冊(cè),不管是單一還是批量,后面我們都會(huì)單獨(dú)提及。階段二:分析和組裝當(dāng)?shù)谝浑A段工作完成后,我們可以先暫且認(rèn)為IoC容器中充斥著一個(gè)個(gè)獨(dú)立的bean,它們之間沒(méi)有任何關(guān)系。但實(shí)際上,它們之間是有依賴關(guān)系的,所以,IoC容器在第二階段要干的事情就是分析這些已經(jīng)在IoC容器之中的bean,然后根據(jù)它們之間的依賴關(guān)系先后組裝它們。如果IoC容器發(fā)現(xiàn)某個(gè)bean依賴另一個(gè)bean,它就會(huì)將這另一個(gè)bean注入給依賴它的那個(gè)bean,直到所有bean的依賴都注入完成,所有bean都“整裝待發(fā)”,整個(gè)IoC容器的工作即算完成。至于分析和組裝的依據(jù),Spring框架最早是通過(guò)XML配置文件的形式來(lái)描述bean與bean之間的關(guān)系的,隨著Java業(yè)界研發(fā)技術(shù)和理念的轉(zhuǎn)變,基于Java代碼和Annotation元信息的描述方式也日漸興盛(比如@Autowired和@Inject),但不管使用哪種方式,都只是為了簡(jiǎn)化綁定邏輯描述的各種“表象”,最終都是為本階段的最終目的服務(wù)。提示很多Java開(kāi)發(fā)者一定認(rèn)為spring的XML配置文件是一種配置(Configuration),但本質(zhì)上,這些配置文件更應(yīng)該是一種代碼形式,XML在這里其實(shí)可以看作一種DSL,它用來(lái)表述的是bean與bean之間的依賴綁定關(guān)系,諸君還記得沒(méi)有IoC容器的年代要自己寫代碼新建(new)對(duì)象并配置(set)依賴的吧?2.3了解一點(diǎn)兒JavaConfigJava5的推出,加上當(dāng)年基于純JavaAnnotation的依賴注入框架Guice的出現(xiàn),使得Spring框架及其社區(qū)也“順應(yīng)民意”,推出并持續(xù)完善了基于Java代碼和Annotation元信息的依賴關(guān)系綁定描述方式,即JavaConfig項(xiàng)目。基于JavaConfig方式的依賴關(guān)系綁定描述基本上映射了最早的基于XML的配置方式,比如:(1)表達(dá)形式層面基于XML的配置方式是這樣的:<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="/schema/beans"

xmlns:xsi="/2001/XMLSchema-instance"

xmlns:context="/schema/context"

xsi:schemaLocation="/schema/beans

/schema/beans/spring-beans.xsd/schema/context/schema/context/spring-context.xsd">

<!--bean

定義

-->

</beans>

而基于JavaConfig的配置方式是這樣的:@Configuration

publicclassMockConfiguration{

//bean

定義

}

任何一個(gè)標(biāo)注了@Configuration的Java類定義都是一個(gè)JavaConfig配置類。(2)注冊(cè)bean定義層面基于XML的配置形式是這樣的:<beanid="mockService"class="..MockServiceImpl">

...

</bean>

而基于JavaConfig的配置形式是這樣的:@Configuration

publicclassMockConfiguration{

@Bean

publicMockServicemockService(){

returnnewMockServiceImpl();

}

}

任何一個(gè)標(biāo)注了@Bean的方法,其返回值將作為一個(gè)bean定義注冊(cè)到Spring的IoC容器,方法名將默認(rèn)成為該bean定義的id。(3)表達(dá)依賴注入關(guān)系層面為了表達(dá)bean與bean之間的依賴關(guān)系,在XML形式中一般是這樣的:<beanid="mockService"class="..MockServiceImpl">

<propertyname="dependencyService"ref="dependencyService"/>

</bean>

<beanid="dependencyService"class="DependencyServiceImpl"/>

而在JavaConfig中則是這樣的:@Configuration

publicclassMockConfiguration{

@Bean

publicMockServicemockService(){

returnnewMockServiceImpl(dependencyService());

}

@Bean

publicDependencyServicedependencyService(){

returnnewDependencyServiceImpl();

}

}

如果一個(gè)bean的定義依賴其他bean,則直接調(diào)用對(duì)應(yīng)JavaConfig類中依賴bean的創(chuàng)建方法就可以了。注意在JavaConfig形式的依賴注入過(guò)程中,我們使用方法調(diào)用的形式注入依賴,如果這個(gè)方法返回的對(duì)象實(shí)例只被一個(gè)bean依賴注入,那也還好,如果多于一個(gè)bean需要依賴這個(gè)方法調(diào)用返回的對(duì)象實(shí)例,那是不是意味著我們就會(huì)創(chuàng)建多個(gè)同一類型的對(duì)象實(shí)例?從代碼表述的邏輯來(lái)看,直覺(jué)上應(yīng)該是會(huì)創(chuàng)建多個(gè)同一類型的對(duì)象實(shí)例,但實(shí)際上最終結(jié)果卻不是這樣,依賴注入的都是同一個(gè)Singleton的對(duì)象實(shí)例,那這是如何做到的?筆者一開(kāi)始以為Spring框架會(huì)通過(guò)解析JavaConfig的代碼結(jié)構(gòu),然后通過(guò)解析器轉(zhuǎn)換加上反射等方式完成這一目的,但實(shí)際上Spring框架的設(shè)計(jì)和實(shí)現(xiàn)者采用了另一種更通用的方式,這在Spring的參考文檔中有說(shuō)明,即通過(guò)攔截配置類的方法調(diào)用來(lái)避免多次初始化同一類型對(duì)象的問(wèn)題,一旦擁有攔截邏輯的子類發(fā)現(xiàn)當(dāng)前方法沒(méi)有對(duì)應(yīng)的類型實(shí)例時(shí)才會(huì)去請(qǐng)求父類的同一方法來(lái)初始化對(duì)象實(shí)例,否則直接返回之前的對(duì)象實(shí)例。所以,原來(lái)SpringIoC容器中有的特性(features)在JavaConfig中都可以表述,只是換了一種形式而已,而且,通過(guò)聲明相應(yīng)的JavaAnnotation反而“內(nèi)聚”一處,變得更加簡(jiǎn)潔明了了。2.3.1那些高曝光率的Annotation至于@Configuration,我想前面已經(jīng)提及過(guò)了,這里不再贅述,下面我們看幾個(gè)其他比較常見(jiàn)的Annotation,便于為后面更好地理解SpringBoot框架的奧秘做準(zhǔn)備。1.@ComponentScan@ComponentScan對(duì)應(yīng)XML配置形式中的<context:component-scan>元素,用于配合一些元信息JavaAnnotation,比如@Component和@Repository等,將標(biāo)注了這些元信息Annotation的bean定義類批量采集到Spring的IoC容器中。我們可以通過(guò)basePackages等屬性來(lái)細(xì)粒度地定制@ComponentScan自動(dòng)掃描的范圍,如果不指定,則默認(rèn)Spring框架實(shí)現(xiàn)會(huì)從聲明@ComponentScan所在類的package進(jìn)行掃描。@ComponentScan是SpringBoot框架魔法得以實(shí)現(xiàn)的一個(gè)關(guān)鍵組件,大家可以重點(diǎn)關(guān)注,我們后面還會(huì)遇到它。2.@PropertySource與@PropertySources@PropertySource用于從某些地方加載*.properties文件內(nèi)容,并將其中的屬性加載到IoC容器中,便于填充一些bean定義屬性的占位符(placeholder),當(dāng)然,這需要PropertySourcesPlaceholderConfigurer的配合。如果我們使用Java8或者更高版本開(kāi)發(fā)(本書寫作期間Java9還沒(méi)發(fā)布),那么,我們可以并行聲明多個(gè)@PropertySource:@Configuration

@PropertySource("classpath:1.properties")

@PropertySource("classpath:2.properties")

@PropertySource("...")

publicclassXConfiguration{

...

}

如果我們使用低于Java8版本的Java開(kāi)發(fā)Spring應(yīng)用,又想聲明多個(gè)@PropertySource,則需要借助@PropertySources的幫助了:@PropertySources({

@PropertySource("classpath:1.properties"),

@PropertySource("classpath:2.properties"),

...

})

publicclassXConfiguration{

...

}

3.@Import與@ImportResource在XML形式的配置中,我們通過(guò)<importresource="XXX.xml"/>的形式將多個(gè)分開(kāi)的容器配置合到一個(gè)配置中,在JavaConfig形式的配置中,我們則使用@Import這個(gè)Annotation完成同樣目的:@Configuration

@Import(MockConfiguration.class)

publicclassXConfiguration{

...

}

@Import只負(fù)責(zé)引入JavaConfig形式定義的IoC容器配置,如果有一些遺留的配置或者遺留系統(tǒng)需要以XML形式來(lái)配置(比如dubbo框架),我們依然可以通過(guò)@ImportResource將它們一起合并到當(dāng)前JavaConfig配置的容器中:@Configuration

@Import(MockConfiguration.class)

@ImportResource("...")

publicclassXConfiguration{

...

}

2.4本章小結(jié)“磨刀不誤砍柴工”,本章我們主要回顧了一下Spring框架的歷史,并對(duì)Spring框架的一些核心功能和特性進(jìn)行了精煉的剖析,在把我們的思維之刀磨礪快了之后,讓我們開(kāi)始解一下SpringBoot這頭小牛兒吧!第3章SpringBoot的工作機(jī)制我們說(shuō)SpringBoot是Spring框架對(duì)“約定優(yōu)先于配置(ConventionOverConfiguration)”理念的最佳實(shí)踐的產(chǎn)物,一個(gè)典型的SpringBoot應(yīng)用本質(zhì)上其實(shí)就是一個(gè)基于Spring框架的應(yīng)用,而如果大家對(duì)Spring框架已經(jīng)了如指掌,那么,在我們一步步揭開(kāi)SpringBoot微框架的面紗之后,大家就會(huì)發(fā)現(xiàn)“陽(yáng)光之下,并無(wú)新事”。不信?那我們一起走著瞧唄!3.1SpringBoot初體驗(yàn)一個(gè)典型的SpringBoot應(yīng)用長(zhǎng)什么樣子呢?如果我們使用\hhttp://start.spring.io/創(chuàng)建一個(gè)最簡(jiǎn)單的依賴Web模塊的SpringBoot應(yīng)用,一般情況下,我們會(huì)得到一個(gè)SpringBoot應(yīng)用的啟動(dòng)類,如下面代碼所示:importorg.springframework.boot.SpringApplication;

importorg.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

publicclassDemoApplication{

publicstaticvoidmain(String[]args){

SpringApplication.run(DemoApplication.class,args);

}

}

所有的SpringBoot無(wú)論怎么定制,本質(zhì)上與上面的啟動(dòng)類代碼是一樣的,而以上代碼示例中,Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)最為耀眼,那么,要揭開(kāi)SpringBoot應(yīng)用的奧秘,很明顯的,我們只要先從這兩位開(kāi)始就可以了。3.2@SpringBootApplication背后的秘密@SpringBootApplication是一個(gè)“三體”結(jié)構(gòu),實(shí)際上它是一個(gè)復(fù)合Annotation:@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@Configuration

@EnableAutoConfiguration

@ComponentScan

public@interfaceSpringBootApplication{

...

}

雖然它的定義使用了多個(gè)Annotation進(jìn)行元信息標(biāo)注,但實(shí)際上對(duì)于SpringBoot應(yīng)用來(lái)說(shuō),重要的只有三個(gè)Annotation,而“三體”結(jié)構(gòu)實(shí)際上指的就是這三個(gè)Annotation:·@Configuration·@EnableAutoConfiguration·@ComponentScan所以,如果我們使用如下的SpringBoot啟動(dòng)類,整個(gè)SpringBoot應(yīng)用依然可以與之前的啟動(dòng)類功能對(duì)等:@Configuration

@EnableAutoConfiguration

@ComponentScan

publicclassDemoApplication{

publicstaticvoidmain(String[]args){

SpringApplication.run(DemoApplication.class,args);

}

}

但每次都寫三個(gè)Annotation顯然過(guò)于繁瑣,所以寫一個(gè)@SpringBoot-Application這樣的一站式復(fù)合Annotation顯然更方便些。3.2.1@Configuration創(chuàng)世紀(jì)這里的@Configuration對(duì)我們來(lái)說(shuō)并不陌生,它就是JavaConfig形式的SpringIoC容器的配置類使用的那個(gè)@Configuration,既然SpringBoot應(yīng)用骨子里就是一個(gè)Spring應(yīng)用,那么,自然也需要加載某個(gè)IoC容器的配置,而SpringBoot社區(qū)推薦使用基于JavaConfig的配置形式,所以,很明顯,這里的啟動(dòng)類標(biāo)注了@Configuration之后,本身其實(shí)也是一個(gè)IoC容器的配置類!很多SpringBoot的代碼示例都喜歡在啟動(dòng)類上直接標(biāo)注@Configuration或者@SpringBootApplication,對(duì)于初接觸SpringBoot的開(kāi)發(fā)者來(lái)說(shuō),其實(shí)這種做法不便于理解,如果我們將上面的SpringBoot啟動(dòng)類拆分為兩個(gè)獨(dú)立的Java類,整個(gè)形勢(shì)就明朗了:@Configuration

@EnableAutoConfiguration

@ComponentScan

publicclassDemoConfiguration{

@Bean

publicControllercontroller(){

returnnewController();

}

}

publicclassDemoApplication{

publicstaticvoidmain(String[]args){

SpringApplication.run(DemoConfiguration.class,args);

}

}

所以,啟動(dòng)類DemoApplication其實(shí)就是一個(gè)標(biāo)準(zhǔn)的Standalone類型Java程序的main函數(shù)啟動(dòng)類,沒(méi)有什么特殊的。而@Configuration標(biāo)注的DemoConfiguration定義其實(shí)也是一個(gè)普通的JavaConfig形式的IoC容器配置類,沒(méi)啥新東西,全是Spring框架里的概念!3.2.2@EnableAutoConfiguration的功效@EnableAutoConfiguration其實(shí)也沒(méi)啥“創(chuàng)意”,各位是否還記得Spring框架提供的各種名字為@Enable開(kāi)頭的Annotation定義?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和“做事方式”其實(shí)一脈相承,簡(jiǎn)單概括一下就是,借助@Import的支持,收集和注冊(cè)特定場(chǎng)景相關(guān)的bean定義:·@EnableScheduling是通過(guò)@Import將Spring調(diào)度框架相關(guān)的bean定義都加載到IoC容器?!EnableMBeanExport是通過(guò)@Import將JMX相關(guān)的bean定義加載到IoC容器。而@EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動(dòng)配置條件的bean定義加載到IoC容器,僅此而已!@EnableAutoConfiguration作為一個(gè)復(fù)合Annotation,其自身定義關(guān)鍵信息如下:@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import(EnableAutoConfigurationImportSelector.class)

public@interfaceEnableAutoConfiguration{

...

}

其中,最關(guān)鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器,就跟一只“八爪魚”一樣(如圖3-1所示)。借助于Spring框架原有的一個(gè)工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以“智能”地自動(dòng)配置功效才得以大功告成!圖3-1EnableAutoConfiguration得以生效的關(guān)鍵組件關(guān)系圖自動(dòng)配置的幕后英雄:SpringFactoriesLoader詳解SpringFactoriesLoader屬于Spring框架私有的一種擴(kuò)展方案(類似于Java的SPI方案java.util.ServiceLoader),其主要功能就是從指定的配置文件META-INF/spring.factories加載配置,spring.factories是一個(gè)典型的javaproperties文件,配置的格式為Key=Value形式,只不過(guò)Key和Value都是Java類型的完整類名(Fullyqualifiedname),比如\h[1]:example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

然后框架就可以根據(jù)某個(gè)類型作為Key來(lái)查找對(duì)應(yīng)的類型名稱列表了:publicabstractclassSpringFactoriesLoader{

//...

publicstatic<T>List<T>loadFactories(Class<T>factoryClass,ClassLoaderclassLoader){

...

}

publicstaticList<String>loadFactoryNames(Class<?>factoryClass,ClassLoaderclassLoader){

...

}

//...

}

對(duì)于@EnableAutoConfiguration來(lái)說(shuō),SpringFactoriesLoader的用途稍微不同一些,其本意是為了提供SPI擴(kuò)展的場(chǎng)景,而在@EnableAutoConfiguration的場(chǎng)景中,它更多是提供了一種配置查找的功能支持,即根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對(duì)應(yīng)的一組@Configuration類:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.admin.SpringApplicationAdmin-JmxAutoConfiguration,\

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\

org.springframework.boot.autoconfigure.PropertyPlaceholderAuto-Configuration,\

org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

org.springframework.boot.autoconfigure.cassandra.CassandraAuto-Configuration,\

org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\

org.springframework.boot.autoconfigure.context.ConfigurationProperties-AutoConfiguration,\

org.springframework.boot.autoconfigure.dao.PersistenceException-TranslationAutoConfiguration,\

org.springframework.boot.autoconfigure.data.cassandra.Cassandra-DataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.cassandra.Cassandra-RepositoriesAutoConfiguration,\

...

以上是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內(nèi)容,可以很好地說(shuō)明問(wèn)題。所以,@EnableAutoConfiguration自動(dòng)配置的魔法其實(shí)就變成了:從classpath中搜尋所有META-INF/spring.factories配置文件,并將其中org.spring-framework.boot.autoconfigure.EnableAutoConfiguration對(duì)應(yīng)的配置項(xiàng)通過(guò)反射(JavaReflection)實(shí)例化為對(duì)應(yīng)的標(biāo)注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個(gè)并加載到IoC容器。目前為止,還是Spring框架的原有概念和支持,依然沒(méi)有“新鮮事”!\h[1]摘自SpringFactoriesLoader的Javadoc。3.2.3可有可無(wú)的@ComponentScan為啥說(shuō)@ComponentScan是可有可無(wú)的?因?yàn)樵瓌t上來(lái)說(shuō),作為Spring框架里的“老一輩革命家”,@ComponentScan的功能其實(shí)就是自動(dòng)掃描并加載符合條件的組件或bean定義,最終將這些bean定義加載到容器中。加載bean定義到Spring的IoC容器,我們可以手工單個(gè)注冊(cè),不一定非要通過(guò)批量的自動(dòng)掃描完成,所以說(shuō)@ComponentScan是可有可無(wú)的。對(duì)于SpringBoot應(yīng)用來(lái)說(shuō),同樣如此,比如我們本章的啟動(dòng)類:@Configuration

@EnableAutoConfiguration

@ComponentScan

publicclassDemoApplication{

publicstaticvoidmain(String[]args){

SpringApplication.run(DemoApplication.class,args);

}

}

如果我們當(dāng)前應(yīng)用沒(méi)有任何bean定義需要通過(guò)@ComponentScan加載到當(dāng)前SpringBoot應(yīng)用對(duì)應(yīng)使用的IoC容器,那么,除去@ComponentScan的聲明,當(dāng)前SpringBoot應(yīng)用依然可以照常運(yùn)行,功能對(duì)等!看,還是沒(méi)有啥新東西!3.3SpringApplication:SpringBoot程序啟動(dòng)的一站式解決方案如果非說(shuō)SpringBoot微框架提供了點(diǎn)兒自己特有的東西,在核心類層面(各種場(chǎng)景下的自動(dòng)配置一站式插拔模塊,我們下一章再重點(diǎn)介紹),也就是SpringApplication了。SpringApplication將一個(gè)典型的Spring應(yīng)用啟動(dòng)的流程“模板化”(這里是動(dòng)詞),在沒(méi)有特殊需求的情況下,默認(rèn)模板化后的執(zhí)行流程就可以滿足需求了;但有特殊需求也沒(méi)關(guān)系,SpringApplication在合適的流程結(jié)點(diǎn)開(kāi)放了一系列不同類型的擴(kuò)展點(diǎn),我們可以通過(guò)這些擴(kuò)展點(diǎn)對(duì)SpringBoot程序的啟動(dòng)和關(guān)閉過(guò)程進(jìn)行擴(kuò)展。最“膚淺”的擴(kuò)展或者配置是SpringApplication通過(guò)一系列設(shè)置方法(setters)開(kāi)放的定制方式,比如,我們之前的啟動(dòng)類的main方法中只有一句:SpringApplication.run

DemoApplication.class

,

args

);

但如果我們想通過(guò)SpringApplication的一系列設(shè)置方法來(lái)擴(kuò)展啟動(dòng)行為,則可以用如下方式進(jìn)行:publicclassDemoApplication{

publicstaticvoidmain(String[]args){

//SpringApplication.run(DemoConfiguration.class,args);

SpringApplicationbootstrap=newSpringApplication(Demo-Configuration.class);

bootstrap.setBanner(newBanner(){

@Override

publicvoidprintBanner(Environmentenvironment,Class<?>aClass,PrintStreamprintStream){

//

比如打印一個(gè)我們喜歡的

ASCIIArts

字符畫

}

});

bootstrap.setBannerMode(Banner.Mode.CONSOLE);

//

其他定制設(shè)置

...

bootstrap.run(args);

}

}

提示設(shè)置自定義banner最簡(jiǎn)單的方式其實(shí)是把ASCIIArt字符畫放到一個(gè)資源文件,然后通過(guò)ResourceBanner來(lái)加載:bootstrap.setBanner(newResourceBanner(newClassPathResource("banner.txt")));大部分情況下,SpringApplication已經(jīng)提供了很好的默認(rèn)設(shè)置,所以,我們不再對(duì)這些表層進(jìn)行探究了,因?yàn)閷?duì)表層之下的東西進(jìn)行探究才是我們的最終目的。3.3.1深入探索SpringApplication執(zhí)行流程SpringApplication的run方法的實(shí)現(xiàn)是我們本次旅程的主要線路,該方法的主要流程大體可以歸納如下\h[1]:1)如果我們使用的是SpringApplication的靜態(tài)run方法,那么,這個(gè)方法里面首先需要?jiǎng)?chuàng)建一個(gè)SpringApplication對(duì)象實(shí)例,然后調(diào)用這個(gè)創(chuàng)建好的SpringApplication的實(shí)例run方法。在SpringApplication實(shí)例初始化的時(shí)候,它會(huì)提前做幾件事情:·根據(jù)classpath里面是否存在某個(gè)特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來(lái)決定是否應(yīng)該創(chuàng)建一個(gè)為Web應(yīng)用使用的ApplicationContext類型,還是應(yīng)該創(chuàng)建一個(gè)標(biāo)準(zhǔn)Standalone應(yīng)用使用的ApplicationContext類型?!な褂肧pringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationContextInitializer?!な褂肧pringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationListener?!ね茢嗖⒃O(shè)置main方法的定義類。2)SpringApplication實(shí)例初始化完成并且完成設(shè)置后,就開(kāi)始執(zhí)行run方法的邏輯了,方法執(zhí)行伊始,首先遍歷執(zhí)行所有通過(guò)SpringFactoriesLoader可以查找到并加載的SpringApplicationRunListener,調(diào)用它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應(yīng)用要開(kāi)始執(zhí)行咯!”。3)創(chuàng)建并配置當(dāng)前SpringBoot應(yīng)用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。4)遍歷調(diào)用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴它們:“當(dāng)前SpringBoot應(yīng)用使用的Environment準(zhǔn)備好咯!”。5)如果SpringApplication的showBanner屬性被設(shè)置為true,則打印banner(SpringBoot1.3.x版本,這里應(yīng)該是基于Banner.Mode決定banner的打印行為)。這一步的邏輯其實(shí)可以不關(guān)心,我認(rèn)為唯一的用途就是“好玩”(JustForFun)。6)根據(jù)用戶是否明確設(shè)置了applicationContextClass類型以及初始化階段的推斷結(jié)果,決定該為當(dāng)前SpringBoot應(yīng)用創(chuàng)建什么類型的ApplicationContext并創(chuàng)建完成,然后根據(jù)條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當(dāng)然,最重要的,將之前準(zhǔn)備好的Environment設(shè)置給創(chuàng)建好的ApplicationContext使用。7)ApplicationContext創(chuàng)建好之后,SpringApplication會(huì)再次借助Spring-FactoriesLoader,查找并加載classpath中所有可用的ApplicationContext-Initializer,然后遍歷調(diào)用這些ApplicationContextInitializer的initialize(applicationContext)方法來(lái)對(duì)已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)行進(jìn)一步的處理。8)遍歷調(diào)用所有SpringApplicationRunListener的contextPrepared()方法,通知它們:“SpringBoot應(yīng)用使用的ApplicationContext準(zhǔn)備好啦!”9)最核心的一步,將之前通過(guò)@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經(jīng)準(zhǔn)備完畢的ApplicationContext。10)遍歷調(diào)用所有SpringApplicationRunListener的contextLoaded()方法,告知所有SpringApplicationRunListener,ApplicationContext"裝填完畢"!11)調(diào)用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。12)查找當(dāng)前ApplicationContext中是否注冊(cè)有CommandLineRunner,如果有,則遍歷執(zhí)行它們。13)正常情況下,遍歷執(zhí)行SpringApplicationRunListener的finished()方法,告知它們:“搞定!”。(如果整個(gè)過(guò)程出現(xiàn)異常,則依然調(diào)用所有SpringApplicationRunListener的finished()方法,只不過(guò)這種情況下會(huì)將異常信息一并傳入處理)。至此,一個(gè)完整的SpringBoot應(yīng)用啟動(dòng)完畢!整個(gè)過(guò)程看起來(lái)冗長(zhǎng)無(wú)比,但其實(shí)很多都是一些事件通知的擴(kuò)展點(diǎn),如果我們將這些邏輯暫時(shí)忽略,那么,其實(shí)整個(gè)SpringBoot應(yīng)用啟動(dòng)的邏輯就可以壓縮到極其精簡(jiǎn)的幾步,如圖3-2所示。圖3-2SpringBoot應(yīng)用啟動(dòng)步驟簡(jiǎn)要示意圖前后對(duì)比我們就可以發(fā)現(xiàn),其實(shí)SpringApplication提供的這些各類擴(kuò)展點(diǎn)近乎“喧賓奪主”,占據(jù)了一個(gè)Spring應(yīng)用啟動(dòng)邏輯的大部分“江山”,除了初始化并準(zhǔn)備好ApplicationContext,剩下的大部分工作都是通過(guò)這些擴(kuò)展點(diǎn)完成的,所以,我們有必要對(duì)各類擴(kuò)展點(diǎn)進(jìn)行逐一剖析,以便在需要的時(shí)候可以信手拈來(lái),為我所用。\h[1]本流程說(shuō)明參考的是SpringBoot1.2.6版本代碼的實(shí)現(xiàn)。3.3.2SpringApplicationRunListenerSpringApplicationRunListener是一個(gè)只有SpringBoot應(yīng)用的main方法執(zhí)行過(guò)程中接收不同執(zhí)行時(shí)點(diǎn)事件通知的監(jiān)聽(tīng)者:publicinterfaceSpringApplicationRunListener{

voidstarted();

voidenvironmentPrepared(ConfigurableEnvironmentenvironment);

voidcontextPrepared(ConfigurableApplicationContextcontext);

voidcontextLoaded(ConfigurableApplicationContextcontext);

voidfinished(ConfigurableApplicationContextcontext,Throwableexception);

}

對(duì)于我們來(lái)說(shuō),基本沒(méi)什么常見(jiàn)的場(chǎng)景需要自己實(shí)現(xiàn)一個(gè)Spring-ApplicationRunListener,即使SpringBoot默認(rèn)也只是實(shí)現(xiàn)了一個(gè)org.spring-framework.boot.context.event.EventPublishingRunListener,用于在SpringBoot啟動(dòng)的不同時(shí)點(diǎn)發(fā)布不同的應(yīng)用事件類型(ApplicationEvent),如果有哪些ApplicationListener對(duì)這些應(yīng)用事件感興趣,則可以接收并處理。(還記得SpringApplication實(shí)例初始化的時(shí)候加載了一批ApplicationListener,但是在run方法執(zhí)行流程中卻沒(méi)有被使用的絲毫痕跡嗎?EventPublishingRunListener就是答案?。┘僭O(shè)我們真的有場(chǎng)景需要自定義一個(gè)SpringApplicationRunListener實(shí)現(xiàn),那么有一點(diǎn)需要注意,即任何一個(gè)SpringApplicationRunListener實(shí)現(xiàn)類的構(gòu)造方法(Constructor)需要有兩個(gè)構(gòu)造參數(shù),一個(gè)構(gòu)造參數(shù)的類型就是我們的org.springframework.boot.SpringApplication,另外一個(gè)就是args參數(shù)列表的String[]:publicclassDemoSpringApplicationRunListenerimplementsSpringApplicationRunListener{

@Override

publicvoidstarted(){

//dowhateveryouwanttodo

}

@Override

publicvoidenvironmentPrepared(ConfigurableEnvironmentenvironment){

//dowhateveryouwanttodo

}

@Override

publicvoidcontextPrepared(ConfigurableApplicationContextcontext){

//dowhateveryouwanttodo

}

@Override

publicvoidcontextLoaded(ConfigurableApplicationContextcontext){

//dowhateveryouwanttodo

}

@Override

publicvoidfinished(ConfigurableApplicationContextcontext,Throwableexception){

//dowhateveryouwanttodo

}

}

之后,我們可以通過(guò)SpringFactoriesLoader立下的規(guī)矩,

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論