springboot 自定義starter的過程以及遇到的問題

前言

??現在微服務火的一塌糊涂,但凡出來面個試,好像你不會微服務就跟你什么都不會一樣。但是像我們這種做外包的小公司,上微服務就不太現實,首先技術支撐不夠,其次開發速度無法滿足快速開發、快速交付的要求,各種服務治理、服務熔斷和降級、鏈路追蹤等等,簡直能把甲方的規劃進度拖死,而且根據我們的經驗來說,找我們的甲方70%的都是沒有自己的技術團隊,這樣交付后即使讓甲方自己組建技術團隊,對他們來說也是一筆更高的成本。
??所以在這種背景下,我們選擇依托springboot的starter特性,把業務邏輯進行封裝,使不同的甲方,重復的項目需求能夠達到即插即拔,達到快速構建、快速開發,節省成本的目的。由此定義名字為spring-lego:像樂高積木一樣,隨意組合,快速成型。 (此項目持續集成中),以下便是在進行starter封裝過程中遇到的問題以及注意的細節。


技術棧

名稱 版本
springboot 2.2.0.RELEASE
mybatisPlus 3.2.0
redis 5.0.6
mysql 5.7
swagger 2.7.0

github:https://github.com/qismyq/spring-lego

starter結構

  • lego-frame-spring-boot-starter(基礎starter,其它所有starter包括主項目直接依賴此starter即可,主工程無需在引入springboot-starter依賴。以下簡稱frame-starter)
    • lego-frame-spring-boot-starter
    • lego-frame-spring-boot-autoconfigure
  • lego-user-spring-boot-starter(用戶業務starter,以下簡稱user-starter)
    • lego-user-spring-boot-starter
    • lego-user-spring-boot-autoconfigure
  • lego-shiro-spring-boot-starter(權限控制starter,需依賴user-starter,以下簡稱shiro-starter)
    • lego-shiro-spring-boot-starter
    • lego-shiro-spring-boot-autoconfigure
  • .....

1. yml配置無法覆蓋使用的問題

問題描述

??本身是希望如果其他starter或者主工程依賴frame-stater時,frame-starter中的yml配置文件可以直接生效或者覆蓋生效,即如果主工程中不配置yml時,即使用frame-starter中的yml配置,如果主工程中配置了相同的配置,則進行覆蓋操作,以主工程中的配置為生效配置。但是當我再主工程中只是配置了spring.server.port,而其他任何都沒有配置時,發現項目啟動失敗,失敗原因為無法創建datasource這個bean 。

問題結論

??此問題只能使用主工程中的yml文件,發現starter在install的時候好像相關的yml文件并沒有被打包進去。即像mybatis或者jdbc一樣,需要在主工程中進行配置。


2. starter中的autoConfig問題

問題描述

??網上很多資料對封裝starter都是一個簡單的示例,大多都是什么xxxServiceAutoConfiguration,然后加一堆@Configuration、@ConditionalOnxxx之類的注解,然后在spring.factories中配置該自動配置類。
??而我遇到的問題是在frame-starter中配置了不止一個autoConfig類,如:DuridConfig、MabatisPlusConfig、RedisConfig等。因為在單體應用中只是在這些配置類上增加@Configuration注解即可,啟動時會自動生效,但是當我封裝好starter后,偶然間發現這些配置類并沒有再主工程的啟動中生效。

解決方案

??經多次測試,關鍵問題有三點:

  1. 在frame-autoconfiguration的spring-factories文件中是否有明確指明自動配置類。
  2. 在frame-autoconfiguration的pom文件中是否有配置spring-boot-maven-plugin插件,此插件的作用是用于springboot依靠java -jar啟動時可以找到主啟動類,如果沒有使用此插件,則打包后使用java -jar命令則會報錯:“frame-spring-boot-autoconfigure-0.0.1-SNAPSHOT.jar中沒有主清單屬性”。
  3. 引入方的主啟動類是否可以掃描到frame-autoconfiguration中的自動配置類。

??其實三點中最關鍵的點是第三點,如果能保證主工程能夠掃描到frame-starter中的配置類,則不需要關注第一和第二點。如果無法保證第三點,則建議使用第一點,保證在spring.factories文件中顯式的指定要自動配置的類。至于第二點,如果在frame-autuconfiguration的pom中引入此插件,則在install時會找主啟動類中的main方法,否則會提示"Unable to find main class",
缺少主啟動類

而當你第一點和第二點都存在的情況下,會報錯“java.lang.IllegalStateException: Unable to read meta-data for class net.yunqihui.starter.config.DruidConfig”(此問題會在下邊詳細描述)。所以建議在封裝starter時一定不要在pom中引用spring-boot-maven-plugin插件以及不要將主啟動類打包進去。

關于第一點:在spring.factories中顯示指定這些配置類的目錄,注意“,”后不要有空格之類的符合,不然會出現無法找到此bean的錯誤。

 # Auto Configure
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 net.yunqihui.starter.config.DruidConfig,\
 net.yunqihui.starter.config.web.SwaggerConfig,\
 net.yunqihui.starter.config.MessageConvertConfig,\
 net.yunqihui.starter.config.MybatisPlusConfig,\
 net.yunqihui.starter.config.RedisConfig,\
 net.yunqihui.starter.config.SchedulerConfig

關于第三點:在主工程的主啟動類上增加compantScan注解,注解的值需要添加stater中的configBean的路徑,以及主工程中的掃描包路徑(注意,此路徑不能丟)

@SpringBootApplication
@MapperScan("net.yunqihui.**.mapper")
@EnableCaching
@ComponentScan({"net.yunqihui","net.yunqihui.config"})
public class SpringBoot08StarterTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBoot08StarterTestApplication.class, args);
    }
}
問題結論

??一開始想的簡單了,認為那些xxxServiceAutoConfiguration就相當于starter的入口而已,只要在spring.factories中配置了此配置類,即可達到starter中的其它的@configuration自動配置。其實并不是,根據springboot的自動配置原理,當開啟了EnableAutoConfiguration(記住這個注解,這個是后續的關鍵)后,會掃描主啟動類所在的包以及子包,但是通常的starter屬于第三方包,包名并不一定和主工程的包名一致,自然無法像單體工程一樣掃描到stater中的Bean。而boot是怎么掃描到上邊舉例的那些xxxServiceAutoConfiguration的呢?是通過spring.factories文件的內容進行查找class然后實現自動配置的。
還記得上邊說的關鍵的EnabaleAutoConfiguration嗎?他的注解上有個元注解@Import,此注解的AutoConfigurationImportSeclector.class中調用方法getCandidateConfigurations中會拿到spring.factories中配置的配置類,所以這便是上述第一點問題。

configurations

??至于上述第三點,原理便是@ComponentScan,它會讓主啟動類掃描當前所在包以及子包,所以如果你的主啟動類是在包net.yunqihui下,同樣各種starter的Bean也是再net.yunqihui包或者子包下,則無需指定spring.factories也可以將配置類加載進容器中,甚至@Controller等各種component組件均可(==關鍵點不在于starter是否是第三方包,而在于掃描路徑==)。

注意: 如果第二點和第三點同時存在,你會發現請求starter中的controller時會有404狀態的錯誤,這說明即使包路徑相同,好像主工程并沒有將starter中的component注入進容器,在我看來,好像并不是沒有被注入,而是在啟動主工程的主啟動類時,啟動的是兩個容器(主工程容器和starter工程容器),而starter中的component被注冊到了后者中。所以,建議任何starter中都不要使用spring-boot-maven-plugin插件,并且在install時把主啟動類排除在外。


3. java.lang.IllegalStateException: Unable to read meta-data for class xxx.xxx.xxx問題

問題描述

封裝starter時,如果你在spring.factories中顯示指定了某些自動配置類,并且在pom文件中使用了spring-boot-maven-plugin插件,且install時將主啟動類打包進去,則在主工程(也即引入方)啟動時,會報如下錯誤:

java.lang.IllegalStateException: Unable to read meta-data for class net.yunqihui.starter.config.DruidConfig
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:233) ~[spring-boot-autoconfigure-2.1.8.RELEASE.jar:2.1.8.RELEASE]
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getOrder(AutoConfigurationSorter.java:204) ~[spring-boot-autoconfigure-2.1.8.RELEASE.jar:2.1.8.RELEASE]
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.access$000(AutoConfigurationSorter.java:150) ~[spring-boot-autoconfigure-2.1.8.RELEASE.jar:2.1.8.RELEASE]
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter.lambda$getInPriorityOrder$0(AutoConfigurationSorter.java:62) ~[spring-boot-autoconfigure-2.1.8.RELEASE.jar:2.1.8.RELEASE]
    at org.springframework.boot.autoconfigure.AutoConfigurationSorter$$Lambda$178/100708535.compare(Unknown Source) ~[na:na]
    at java.util.TimSort.countRunAndMakeAscending(Unknown Source) ~[na:1.8.0_25]
解決方案

關于此錯誤,有很多資料會有以下幾點說法:

  1. spring.factories中有一些肉眼不可見的錯誤,比如“,”后有不小心多出一個空格的
  2. starter間的傳遞依賴,導致無法找到此class,比如主工程依賴frame-starter,social-starter也依賴frame-starter,但是同時主工程也依賴了social-starter,這個時候會出現重復依賴的問題,那么則需要將social-stater的依賴排除掉frame-starter,如下:
    social-starter的pom依賴
    主工程的pom依賴

    但是對于以上解決方案,我發現我的沒有任何改變(對不起,我上邊只是多提供一些解決手段,不是故意啰嗦),而我這邊出現此問題的原因好像就是上邊所說的,貌似啟動了兩個容器,主容器之所以找不到這個class,是因為這個class被注入到了starter中的容器中。要解決此問題,還是要干掉spring-boot-maven-plugin插件和starter的啟動類。

4. 到底是分兩個模塊還是一個模塊

問題描述

??一開始我所了解的自定義一個starter,是需要一個auto configure模塊和一個starter模塊的,即前者負責具體的處理,而starter模塊只是一個空殼,只是對外依賴的一個門戶而已。但是后來當我先發布到中央倉庫上時,發現需要將兩個模塊都進行打包發布,我就有點開始懷疑了,這樣做的意義是什么,那么有人使用時直接依賴autoconfigure,而不使用starter模塊不是浪費了嗎?

問題結果

??我參考了mybatisplus的starter以及其他第三方的starter,發現他們都是直接只搞一個starter,autoconfigure直接就在這里邊了。
mybatis-plus starter

我不死心,因為看到spring-boot-starter-redis等這些官方starter都是有對外暴露的starter,還區分實際執行的autoconfigure,總覺得這不符合規范,一直到在spring.io上看到了官方描述
spring.io

既然官方都這樣說了,那也沒什么糾結的了,直接更改為一個module。

5. starter中的schema.sql無法執行

問題描述

??springboot中提供應用程序啟動時執行sql腳本,通常為在yml中添加spring.datasource.initialization-mode: always(2.x版本),這樣在應用程序啟動時找classpath:schema.sql(我一開始的顯式配置)文件。我的本意是在每個業務starter中配置相應的schema.sql,用來創建相應業務表結構。但是測試時發現,并沒有執行自定義starter中的相關sql腳本。

解決方案

??其實這個問題完全跟我畫蛇添足有關,springboot的DataSourceInitializer.class中的createSchema()雖然會傳入spring.datasource.schema的配置,且默認fallback參數為schema。,但是getScripts()中會先判斷resources是否為null,不為null,則執行顯示配置的文件路徑,當發現配置文件中沒有相關配置時,才會查找classpath:下的schema.sql和schema-all.sql文件。

createSchema
getscripts

如果不是我畫蛇添足的顯示指定加載腳本路徑,其實并不會出現無法執行starter中的腳本文件的問題。
由此,此問題的根本原因是classpath:和classpath
:的區別(前者為主項目的class路徑,后者為包含第三方jar文件中進行查找)的問題。所以即使我顯示配置schema為classpath*:schema.sql也是可以的。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372