使用Spring Boot實現模組化

banq發表於2018-07-25
一般情況下,一個SpringBoot應用 = 一個微服務 = 一個模組 = 一個有邊界的上下文,如果有多個模組,我們就開發多個微服務,多個SpringBoot應用,然後使用Springcloud實現它們之間動態訪問和監控。 但是有時我們也會希望將多個模組放入一個SpringBoot應用中,這樣模組之間呼叫可以在一個JVM內進行,適合小型系統的部署,隨著規模擴大,我們還可將這些模組變成一個個微服務,以SpringBoot應用分散式執行。

SpringBoot為模組化提供了非常直接簡單的組合方式,可以說完全替代OSGI或其他模組外掛技術。

什麼是Spring Boot中的模組?
本文意義上的“模組”是一組載入到應用程式上下文中的Spring元件。

模組可以是業務模組,為應用程式提供一些業務服務,或者為幾個其他模組或整個應用程式提供跨領域關注的技術模組。

建立模組的幾種辦法
Spring模組的基礎是一個@Configuration註釋,這是一種Spring的Java配置特性,可以用來標註在你的模組配置類中,配合@Configuration有幾種更細粒度的方式:

(1)@ComponentScan
建立模組的最簡單方法是使用@ComponentScan註釋:


@Configuration
@ComponentScan(basePackages = "io.reflectoring.booking")
public class BookingModuleConfiguration {
}



如果這個配置類由importing 機制(稍後解釋)匯入的一個,它將檢視包io.reflectoring.booking中的所有類,如果使用了Spring的構造型註釋中任何一個註釋,這些類的例項將被載入到Spring的應用上下文中。

只要你總是希望將包及其子包的所有類載入到Spring應用上下文中,那麼使用這種方式就可以了。如果你需要更多控制載入內容,請繼續。。

(2)@Bean 定義
Spring的Java配置功能還有一個@Bean註釋,用於建立載入bean的例項到Spring應用上下文中:

@Configuration
public class BookingModuleConfiguration {

  @Bean
  public BookingService bookingService(){
    return new BookingService();
  }
  
  // potentially more @Bean definitions ...

}
<p class="indent">

匯入此配置類時,BookingService例項將被建立並插入Spring的應用上下文中。

使用這種方式進行模組的建立就可以更清楚地瞭解實際載入的bean,因為你只需要檢視一個地方(配置類),更方便 ,這種辦法與使用@ComponentScan地方相比,後者需要你檢視包中所有類的構造型註釋,看看是什麼構造型,符合條件才能被載入。

(3)@Conditional 註釋
如果你需要對哪些元件應該載入到Spring應用上下文中要進行更細粒度的控制,則可以使用Spring Boot的@Conditional...註釋:

@Configuration
@ConditionalOnProperty(name = "io.reflectoring.security.enabled", 
    havingValue = "true", matchIfMissing = true)
public class SecurityModuleConfiguration {
  // @Bean definitions ...
}
<p class="indent">

在使用這個模組時,必須在application配置檔案中設定屬性io.reflectoring.security.enabled為true才能使用這個模組。(見後面使用模組)

還可以使用其他@Conditional...註釋來定義載入模組的條件。有一個依賴條件,具體取決於JVM的版本以及某個類是否存在於類路徑中或某個bean是否存在於Spring應用上下文中。

如果你曾經問​​過自己Spring Boot如何神奇地將應用程式所需的bean載入到應用程式上下文中,原理就在於使用了這個註釋@Conditional,Spring Boot本身大量使用@Conditional註釋。

以上三種辦法是建立一個模組的方式,那麼如何使用這些模組呢?也有幾種方式可選,注意,要分清模組建立和模組使用兩個大的邊界。

使用模組的幾種辦法
建立模組後,我們需要將其匯入到SpringBoot應用程式中,有下面幾種辦法:

(1)@Import
最直接的方法是使用@Import註釋:

@SpringBootApplication
@Import(BookingModuleConfiguration.class)
public class ModularApplication {
  // ...
}
<p class="indent">

這將匯入BookingModuleConfiguration類及其隨附的所有bean - 無論它們是由宣告@ComponentScan還是@Bean註釋。

(2)@Enable... 註釋
Spring Boot帶有一組註釋,每個註釋都自己匯入某個模組。一個例子是@EnableScheduling,它匯入排程子系統所需的所有Beans及其@Scheduled註釋,也就是說,如果你在你的應用類中使用了@Scheduled註釋,如果想使得這種排程功能起效,還必須在入口處加入@EnableScheduling,否則就不起效,這也是SpringBoot使用中容易掉的坑,關鍵還是沒有了解Spring的模組機制:

@SpringBootApplication
@EnableScheduling
public class SpringbatchApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbatchApplication.class, args);
	}
}
<p class="indent">


我們也可以匯入自己的Enable配置:

@SpringBootApplication
@EnableBookingModule
public class ModularApplication {

	public static void main(String[] args) {
		SpringApplication.run(ModularApplication.class, args);
	}

}
<p class="indent">

上面程式碼中EnableBookingModule不是Spring自己的註釋,而是我們自己的定做的,程式碼如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(BookingModuleConfiguration.class)
@Configuration
public @interface EnableBookingModule {
}
<p class="indent">


該@EnableBookingModule註釋實際上只是包裝了@Import,首先匯入我們的BookingModuleConfiguration,如果我們有一個模組是由多個配置類組成,這種辦法是一種將這些配置類聚合到單個模組中的方便且富有表現力的方法。

(3)自動配置Auto-Configuration
如果我們想自動載入模組而不是將之前那樣在原始碼中匯入指定的硬連線hard-wiring,我們可以使用Spring Boot的自動配置功能,也就是不再原始碼中使用註釋,而是使用配置檔案。

請在模組所在專案下(注意,不是模組使用的專案)建立檔案META-INF/spring.factories,執行時需要放入classpath類路徑中 ,在該檔案中寫入:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  io.reflectoring.security.SecurityModuleConfiguration
<p class="indent">

多個配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
<p class="indent">


該模組的使用者所在Springboot專案啟動時會將SecurityModuleConfiguration類的所有bean匯入到Spring應用上下文中。

要在SpringBoot中使用這個SecurityModuleConfiguration,還需要在模組使用的專案中在application.yml中定義:

io:
  reflectoring:
    security:
      enabled: true
<p class="indent">

這裡將io.reflectoring.security.enabled設定true,是對應前面該模組建立時使用@Conditional註釋時有一個條件:

@ConditionalOnProperty(name = "io.reflectoring.security.enabled", 
    havingValue = "true", matchIfMissing = true)
<p class="indent">



使用模組的策略
前面介紹了在Spring Boot應用程式中使用模組的幾個辦法,但是我們什麼時候在什麼情況下選擇哪一個呢?

(1)業務模組使用@Import
對於包含業務邏輯的模組 - 比如上面的BookingModuleConfiguration - 在大多數情況下使用@Import,使用帶註釋的靜態匯入應該足夠了。通常那些沒有載入業務模組也是沒有意義的,因此我們不需要對它們的載入條件進行任何控制。


(2)技術模組使用自動配置
另一方面,技術性的模組 - 如安全SecurityModuleConfiguration - 這些技術通常會提供一些跨域的切面關注(類似AOP),例如日誌記錄,異常處理,授權或監視功能,這些功能在開發和執行時需求不一樣,在開發過程中,可能根本不需要這些功能,因此我們希望有一種方法來禁用它們。

我們不希望使用@Import靜態地匯入每個技術模組,因為它們不應該對我們的程式碼產生任何影響。

因此,使用技術模組的最佳選擇是自動配置功能。模組在後臺靜默載入,我們可以使用在程式碼之外配置屬性中影響它們。

本文案例:github

Modularizing a Spring Boot Application - Reflector

相關文章