最近接手一套基于SpringBoot專案,對專案進行重構調整,將公共部分抽離成子專案,在實踐的程序中,發現抽離之后的模板中組件并沒有被初始化,于是將排查解決程序中搜集到的方案及知識匯總分享給大家,
問題原因
問題的原因很簡單,因多套系統的package命名不一致,比如業務系統的包命名為com.abc.xx,而公共(common)部分的包命名為com.efg.xx,引入公共jar包時默認是無法初始化的,

對于SpringBoot專案,我們知道掃描的路徑從啟動類所在包開始,掃描當前包及其子級包下的所有檔案,上圖如果啟動類在com.abc包下,肯定是無法掃描到com.def包內的組件的,
場景延伸
SpringBoot的這個機制還延伸出另外兩個場景,
第一個場景是如果SpringBoot的啟動類放的包路徑靠下,那么在它上級目錄中的組件是無法被掃描并初始化的,新手往往會因放錯位置導致啟動時例外,
第二個場景是故意將一些不需要納入SpringBoot容器的類放在其他包中,避免被SpringBoot容器加載,當然此時也可以使用ComponentScan來指定排除對應的包,
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── secbro
│ │ │ ├── SpringBootMainApplication.java
│ │ │ ├── controller
│ │ │ │ └── DruidController.java
│ │ │ ├── model
│ │ │ │ └── Order.java
│ │ │ └── service
│ │ │ ├── OrderService.java
│ │ │ └── impl
│ │ │ └── OrderServiceImpl.java
上述專案結構中,如果將類直接放在com目錄或com目錄的其他子目錄下,默認是不會被初始化的,
通過@ComponentScan掃描
回到正題,遇到類似不被初始化的情況,我們可以使用的最簡單的方案就是手動指定掃描包路徑,
在啟動類上的@SpringBootApplication注解內部集成了@ComponentScan注解,此時我們可以顯示的指定掃描的包,
@SpringBootApplication
@ComponentScan({"com.abc.xx","com.def.xx"})
public class SpringBootMainApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMainApplication.class, args);
}
}
此種用法一定要先包含本專案要掃描的路徑“com.abc.xx”,然后再在后面添加上common專案要掃描的路徑“com.def.xx”,
如果其他專案不需要初始化common中的內容,則可不進行指定,
自定義@Enable****注解
上述方法雖然能夠解決問題,但如果直接寫包名,難免沒有個統一的規范,此時可考慮使用@Enable型別的注解,
了解SpringBoot機制的朋友都知道,最重要的一個注解便是@EnableAutoConfiguration,類似的,我們定義一個可以通過注解之后便可使用的Enable注解,

定義配置類,在配置類中指定要掃描的包路徑:
@Component
@ComponentScan("com.def.xx")
public class CommonConfig {
}
定義Enable注解類,并通過@Import匯入配置類:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CommonConfig.class)
public @interface EnableCommon {
}
然后,在啟動類中便可使用@EnableCommon此注解來指定實體化對應的package,
@EnableCommon
@SpringBootApplication
public class SpringBootMainApplication {
// ...
}
在此程序中需要注意的是CommonConfig是位于common專案當中的,如果CommonConfig直接可被SpringBoot掃描到,那也就不需要EnableCommon注解了,
自定義starter
我們使用SpringBoot之所以方便,得益于它的特性之一便是可以使用已經集成好的starter,同樣,我們也可以自定義一套starter來達到自動化配置的效果,
由于這種模式更適用于自動化集成某一個組件,并不太適合這里說的common公共專案,因此就不再代碼演示,只說一下大概的思路,詳細實體可參考我的新書《SpringBoot技術內幕:架構設計與實作原理》,
定義starter首先需要依賴自動配置的組件,也就是pom檔案中添加如下配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
然后再定義具體的服務(或初始化)類,比如HelloWorldService以及該服務類初始化的引數類HelloWorldProperties,通過@ConfigurationProperties注解可以將Application中對應的屬性初始化到類的屬性中,
然后呢,再提供一個基于@ConditionalOnClass配置的HelloWorldAutoConfiguration類,指定當HelloWorldService存在于類路徑時,便會進行初始化,
最后一步,在META-INF目錄下創建spring.factories,啟動添加類似如下配置:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.secbro.HelloWorldAutoConfiguration
該類是為SpringBoot提供的掃描入口,
此時,當其他專案需要該starter時,直接引入便可注入使用HelloWorldService類了,
關于此處建議大家專門看一篇相關的實戰文章,可以更好的理解,這里只提供了一個大概的思路,
小結
關于SpringBoot的@ComponentScan基本上已經可以滿足需求了,第二種方案是基于@ComponentScan的改進方案,而第三種方案更多的是基于SpringBoot的核心原理來處理的,當然最好是避免同一個專案使用多個頂級package,
通過本篇文章的脈絡,我們可以看到一種學習的方式,通過一個知識點或一個實戰中的問題,可以逐步將知識從點擴充到面,這樣不僅能加大學習的范圍,也能構建更牢固的知識圖譜,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/179012.html
標籤:Java
