因业务需要, 公司内需要使用 SpringBoot Starter 构建 SDK. 不同的是使用了更为灵活的 Kotlin 语言, 构建脚本也换成了 Kotlin Script.
本文主要分几个步骤:
不会太详细, 但会把主要的内容和要注意的点记录下来.
SpringBoot Starter 实现的原理网络上已经有很多, 就不细说了, 我总结了一下核心的运作逻辑, 就是下面我画的这张图:
所以要写一个 starter, 无论用什么语言本质上都是一样的.
以下步骤可能与部分网络教程不太一样, 主要是根据上面的图方向来分析说明的, 是一个按照逻辑需求来定义的顺序:
实际写代码时顺序按需即可.
比如, 我想实现一个邮件告警的 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 # 接收方邮箱
怎么实现呢?
看个总体目录结构(已删减无关文件):
├── 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")
这里的属性就定义了配置文件的写法.
@ConfigurationProperties(prefix = "simple.alarm.email")
data class EmailProperties(
var host? = null,
var senderEmail? = null,
var senderPassword? = null,
var receiverEmail? = null
)
注意:
属性声明好了, 该到用的时候了.
class AlarmByEmail(
private val host,
private val senderEmail,
private val senderPassword,
private val receiverEmail
) {
fun sendMessage(content: String): Boolean {
// 发邮件的实现
}
}
此处使用了构造器注入的方式, 也可以使用 setter 方式.
这是关键, 上面配置上的属性和业务 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
)
}
}
就是如此简单.
这个文件是上面写好的自动配置的入口, 有了它 Spring 才能读到上面写好的内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.looko.simplealarmspringbootstarter.autoconfigure.SimpleAlarmAutoConfiguration
可不写, 用来作为写属性时的提示.
可不写, 用来作为写属性时的提示.
{
"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
)
}
}
这样就全部完成了.
测试是必要的.
单独的 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"))
}
}
使用 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
根据实际业务集成, 具体代码略.