首頁 > 軟體

KotlinScript構建SpringBootStarter保姆級教學

2022-09-28 14:01:03

引言

因業務需要, 公司內需要使用 SpringBoot Starter 構建 SDK. 不同的是使用了更為靈活的 Kotlin 語言, 構建指令碼也換成了 Kotlin Script.

  • 框架: SpringBoot
  • 業務程式碼語言: Kotlin
  • 構建工具: Gradle
  • 構建指令碼: Kotlin Script (不同於 Groovy, 是 Kotlin 自家的 DSL, 檔案字尾為 .kts)
  • 開發工具: Idea CE

本文主要分幾個步驟:

  • 用 Kotlin 寫一個簡單 SpringBoot Starter
  • 進階一: 複雜設定引數的寫法
  • 進階二: starter 單元測試
  • 使用 Kotlin Script 構建成 Maven 依賴
  • 整合測試

不會太詳細, 但會把主要的內容和要注意的點記錄下來.

一 如何用 Kotlin 寫一個簡單 SpringBoot Starter

1 分析

SpringBoot Starter 實現的原理網路上已經有很多, 就不細說了, 我總結了一下核心的運作邏輯, 就是下面我畫的這張圖:

所以要寫一個 starter, 無論用什麼語言本質上都是一樣的.

以下步驟可能與部分網路教學不太一樣, 主要是根據上面的圖方向來分析說明的, 是一個按照邏輯需求來定義的順序:

  • resources 下新建 META-INF 資料夾, 寫個 spring.factories 檔案 (檔案內容見後文), 用於指定一個設定類.
  • 寫設定類, 主要職能是業務 Bean 與 其相關設定的樞紐, 它將對業務 Bean 進行設定, 設定的內容來源於後面我們自己定義的組態檔寫法.
  • 寫業務 Bean, 也就是想讓別人參照這個 starter 依賴後可以使用的類.
  • 寫設定屬性宣告類, 是個 POJO 類, 宣告了可以在 application.properties 或者 application.yml 裡能使用的設定屬性
  • 可選, 寫一個 json 檔案用於給使用者寫 application.properties 的時候提示一些資訊

實際寫程式碼時順序按需即可.

2 簡單案例設計

比如, 我想實現一個郵件告警的 SDK.

這個 SDK 有一個類 AlarmByEmails, 整合此 SDK 的專案通過如下的 application.properties 設定後, 可通過 AlarmByEmails 的某個方法呼叫 xxx@163.com 傳送郵件給 yyy@163.com.

(考慮到後續 starter 測試用 yml 方式有所不便, 所以 starter 中測試使用 properties 檔案)

simple.alarm.email.host=smtp.163.com # 郵件協定伺服器
simple.alarm.email.senderEmail=xxx@163.com	# 傳送方郵箱
simple.alarm.email.senderPassword=xxx	# 傳送方郵箱的授權碼, 非密碼
simple.alarm.email.receiverEmail=yyy@163.com	# 接收方郵箱

怎麼實現呢?

3 程式碼實現

看個總體目錄結構(已刪減無關檔案):

├── build.gradle.kts
├── settings.gradle.kts
└── src
    └── main
        ├── kotlin
        │   └── com
        │       └── looko
        │           └── simplealarmspringbootstarter
        │               ├── autoconfigure
        │               │   ├── SimpleAlarmAutoConfiguration.kt
        │               │   └── properties
        │               │       └── EmailProperties.kt
        │               └── component
        │                   └── AlarmByEmails.kt
        └── resources
            ├── META-INF
            │   └── spring.factories
            └── test.properties

依賴項

基於 Kotlin 和 Gradle 新建 Spring Boot 專案, 名稱最好按照 Starter 建立的約定俗成規範 xxx-spring-boot-starter , 刪除啟動類, 然後在 build.gradle.kts 的依賴中新增:

annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")

設定屬性宣告類: xxxProperties

這裡的屬性就定義了組態檔的寫法.

@ConfigurationProperties(prefix = "simple.alarm.email")
data class EmailProperties(
  var host? = null,
  var senderEmail? = null,
  var senderPassword? = null,
  var receiverEmail? = null
)

注意:

  • 組態檔到 POJO 的屬性裝配是要用到 setter 的, 所以要定義為 var, 如果定義為 val , starter 被參照後, 程式啟動階段在讀取相應設定時, 如果檔案設定與預設設定不一樣的話會報錯.
  • Spring 在處理這個類的時候會自動屬性注入, 如果不寫預設值的話啟動找不到注入值會報錯.

業務 Bean

屬性宣告好了, 該到用的時候了.

class AlarmByEmail(
  private val host,
  private val senderEmail,
  private val senderPassword,
  private val receiverEmail
) {
  fun sendMessage(content: String): Boolean {
    // 發郵件的實現
  }
}

此處使用了構造器注入的方式, 也可以使用 setter 方式.

設定類: xxxAutoConfiguration

這是關鍵, 上面設定上的屬性和業務 Bean 都有了, 如何把它倆關聯起來並註冊成 Spring Bean 呢?

@Configuration
@ConditionalOnClass(SimpleAlarmAutoConfiguration::class)
@EnableConfigurationProperties(value = [EmailProperties::class])
class SimpleAlarmAutoConfiguration {
    @Bean
    fun alarmByEmail(properties: EmailProperties): AlarmByEmail {
        return AlarmByEmail(
          properties.host,
          properties.senderEmail,
          properties.senderPassword,
          properties.receiverEmail
        )
    }
}

就是如此簡單.

@Configuration + @Bean 老組合了, 將一個類註冊為 Spring Bean.

@ConditionalOnClass, 是基於 @Conditional 的條件註解, 是 Spring4 提供的一種註解, 它的作用是按照設定的條件進行判斷, 把滿足判斷條件的 Bean 註冊到 Spring 容器. 相關注解如下:

條件註解作用
@ConditionalOnBean當上下文存在某個物件時才會範例化 Bean
@ConditionalOnClass某個 Class 位於 classpath 路徑上才會範例化 Bean
@ConditionalOnExpression當 SpEL 表示式值為 true 的時候才會範例化 Bean
@ConditionalOnMissingBean當上下文不存在某個物件時才會範例化 Bean
@ConditionalOnMissingClass某個 Class 不在 classpath 路徑上才會範例化 Bean
@ConditionalOnNotWebApplication非 web 應用才會範例化 Bean
@ConditionalOnWebApplicationweb 應用才會範例化 Bean
@ConditionalOnProperty當指定的屬性有指定的值時才會範例化 Bean
@ConditionalOnJava當 JVM 版本為指定的版本範圍時才會範例化 Bean
@ConditionalOnResource當 classpath 路徑下有指定的資源時才會範例化 Bean
@ConditionalOnJndi在 JNDI 存在時才會範例化 Bean
@ConditionalOnSingleCandidate當指定的 Bean 在容器中只有一個, 或者有多個但是指定了首選的 Bean 時, 才會範例化 Bean

@EnableConfigurationProperties , 用於獲取設定宣告類, 原理不贅述.

spring.factories 檔案

這個檔案是上面寫好的自動設定的入口, 有了它 Spring 才能讀到上面寫好的內容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.looko.simplealarmspringbootstarter.autoconfigure.SimpleAlarmAutoConfiguration

json 設定註釋檔案

可不寫, 用來作為寫屬性時的提示.

spring-configuration-metadata.json :

{
  "properties": [
    {
      "name": "simple.alarm.email.host",
      "type": "java.lang.String",
      "description": "郵件伺服器地址."
    },
    {
      "name": "simple.alarm.email.senderEmail",
      "type": "java.lang.String",
      "description": "傳送者郵箱."
    },
    {
      "name": "simple.alarm.email.senderPassword",
      "type": "java.lang.String",
      "description": "傳送者授權碼."
    },
    {
      "name": "simple.alarm.email.receiverEmail",
      "type": "java.lang.String",
      "description": "接收者郵箱."
    },
  ]
}

二 進階: 複雜設定引數的寫法

如果我想通過設定配多個傳送者的郵箱, 每個郵箱又可以設定, 該如何實現呢?

比如, 使用 xxx@163.com 傳送郵件給 yyy@163.com, 而使用 yyy@163.com 則可以同時發郵件給 zzz@163.com 和 xxx@163.com.

設定的寫法:

simple.alarm.email.configs[0].host=smtp.163.com
simple.alarm.email.configs[0].senderEmail=xxx@163.com
simple.alarm.email.configs[0].senderPassword=xxx
simple.alarm.email.configs[0].receivers[0]=yyy@163.com
simple.alarm.email.configs[1].host=smtp.163.com
simple.alarm.email.configs[1].senderEmail=yyy@163.com
simple.alarm.email.configs[1].senderPassword=yyy
simple.alarm.email.configs[1].receivers[0]=zzz@163.com
simple.alarm.email.configs[1].receivers[0]=xxx@163.com

將郵箱按傳送者分成了一個個的 configs 陣列, 每個 configs 下面儲存了傳送的設定, 同時接收者也設定成了陣列,

這樣就完美符合需求了.

那麼 properties 等類怎麼寫呢?

EmailProperties:

@ConfigurationProperties(prefix = "simple.alarm.email")
data class EmailProperties(
    var configs: Array<EmailConfigEntity> = arrayOf()
)

這是抽出來的 EmailConfigEntity, 注意用 var:

data class EmailConfigEntity(
    var host: String? = null,
    var senderEmail: String? = null,
    var senderPassword: String? = null,
    var receivers: Array<String> = arrayOf()
)

因為引數抽出來了, 所以 AlarmByEmail 的入參也要相應調整:

class AlarmByEmail(
  private val configs: Array<EmailConfigEntity>
) {
  fun sendMessage(content: String): Boolean {
    // 發郵件的實現
  }
}

SimpleAlarmAutoConfiguration 相應調整:

@Configuration
@ConditionalOnClass(SimpleAlarmAutoConfiguration::class)
@EnableConfigurationProperties(value = [EmailProperties::class])
class SimpleAlarmAutoConfiguration {
    @Bean
    fun alarmByEmail(properties: EmailProperties): AlarmByEmail {
        return AlarmByEmail(
          properties.configs
        )
    }
}

這樣就全部完成了.

三 進階: Starter 單元測試

測試是必要的.

單獨的 Spring-boot-starter 並不是一個完整的應用 大多數時候都是作為一個實際應用的一部分存在 如果是通過另一個專案參照並啟動專案的話, 會在 Debug 時造成不必要的麻煩 所以需要建立能夠獨立執行的 Test

依賴

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure")

組態檔

resourses 路徑下的 test.properties:

simple.alarm.email.configs[0].host=smtp.163.com
simple.alarm.email.configs[0].senderEmail=xxx@163.com
simple.alarm.email.configs[0].senderPassword=xxx
simple.alarm.email.configs[0].receivers[0]=yyy@163.com
simple.alarm.email.configs[1].host=smtp.163.com
simple.alarm.email.configs[1].senderEmail=yyy@163.com
simple.alarm.email.configs[1].senderPassword=yyy
simple.alarm.email.configs[1].receivers[0]=zzz@163.com
simple.alarm.email.configs[1].receivers[0]=xxx@163.com

測試類

如下, 通過註解指定自動設定類和組態檔

@SpringBootTest(classes = [SimpleAlarmAutoConfiguration::class])
@TestPropertySource("classpath:test.properties")
class SimpleAlarmSpringBootStarterApplicationTests {
    @Test
    fun contextLoads() {
    }
    @Autowired
    lateinit var alarmByEmail: AlarmByEmail
    @Test
    fun testAlarmByEmail() {
        assert(alarmByEmail.sendMessage("Message Content"))
    }
}

四 如何使用 Kotlin Script 構建成 Maven 依賴

使用 maven-publish 外掛.

build.gradle.kts 中, 主要用法如下 :

// ...
plugins {
    // ...
    `maven-publish`
}
// ...
val sourcesJar by tasks.registering(Jar::class) {
    archiveClassifier.set("sources")
    from(sourceSets.main.get().allSource)
}
publishing {
    publications {
        register("alarm", MavenPublication::class) {
            groupId = "com.looko"
            artifactId = "simple-alarm-spring-boot-starter"
            version = "0.0.1-SNAPSHOT"
            from(components["java"])
            artifact(sourcesJar.get())
        }
    }
    repositories {
        maven {
            mavenLocal()
        }
    }
}
// ...

在 IDEA 介面 double-ctrl 撥出 run 視窗, 找到 gradle publishToMavenLocal 回車就能打包到 .m2 目錄下了.

或者在右側 gradle 視窗中也能找到相應的 gradle task.

如果打到倉庫的包裡含有 plain 字尾, 不被 maven 識別的話, 可以在 build.gradle.kts 中新增如下設定解決:

tasks.getByName<Jar>("jar") {
	archiveClassifier.set("")
}

五 整合測試

依賴

testImplementation("com.looko:simple-alarm-spring-boot-starter:0.0.1-SNAPSHOT")

組態檔

application.properties

simple.alarm.email.configs[0].host=smtp.163.com
simple.alarm.email.configs[0].senderEmail=xxx@163.com
simple.alarm.email.configs[0].senderPassword=xxx
simple.alarm.email.configs[0].receivers[0]=yyy@163.com
simple.alarm.email.configs[1].host=smtp.163.com
simple.alarm.email.configs[1].senderEmail=yyy@163.com
simple.alarm.email.configs[1].senderPassword=yyy
simple.alarm.email.configs[1].receivers[0]=zzz@163.com
simple.alarm.email.configs[1].receivers[0]=xxx@163.com

或者 application.yml

simple:
  alarm:
    email:
      configs:
        - host: smtp.163.com
            senderEmail: xxx@163.com
            senderPassword: xxx
            receivers:
              - yyy@163.com
        - host: smtp.163.com
            senderEmail: yyy@163.com
            senderPassword: yyy
            receivers:
              - zzz@163.com
              - xxx@163.com

程式碼

根據實際業務整合, 具體程式碼略.

範例程式碼:

gayhub 倉庫

以上就是KotlinScript 構建 SpringBootStarter保姆級教學的詳細內容,更多關於KotlinScript 構建 SpringBootStarter的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com