您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

保姆级,使用 KotlinScript 构建 SpringBootStarter

时间:2022-09-27 14:01:06  来源:  作者:在天上飞的的程序员

因业务需要, 公司内需要使用 SpringBoot Starter 构建 SDK. 不同的是使用了更为灵活的 Kotlin 语言, 构建脚本也换成了 Kotlin Script.

  • 框架: SpringBoot
  • 业务代码语言: Kotlin
  • 构建工具: Gradle
  • 构建脚本: Kotlin Script (不同于 Groovy, 是 Kotlin 自家的 DSL, 文件后缀为 .kts)
  • 开发工具: Idea CE

本文主要分几个步骤:

  1. 用 Kotlin 写一个简单 SpringBoot Starter
  2. 进阶一: 复杂配置参数的写法
  3. 进阶二: starter 单元测试
  4. 使用 Kotlin Script 构建成 Maven 依赖
  5. 集成测试

不会太详细, 但会把主要的内容和要注意的点记录下来.

一 如何用 Kotlin 写一个简单 SpringBoot Starter

1 分析

SpringBoot Starter 实现的原理网络上已经有很多, 就不细说了, 我总结了一下核心的运作逻辑, 就是下面我画的这张图:

 

 

所以要写一个 starter, 无论用什么语言本质上都是一样的.

以下步骤可能与部分网络教程不太一样, 主要是根据上面的图方向来分析说明的, 是一个按照逻辑需求来定义的顺序:

  1. 在 resources 下新建 META-INF 文件夹, 写个 spring.factories 文件 (文件内容见后文), 用于指定一个配置类.
  2. 写配置类, 主要职能是业务 Bean 与 其相关配置的枢纽, 它将对业务 Bean 进行配置, 配置的内容来源于后面我们自己定义的配置文件写法.
  3. 写业务 Bean, 也就是想让别人引用这个 starter 依赖后可以使用的类.
  4. 写配置属性声明类, 是个 POJO 类, 声明了可以在 Application.properties 或者 application.yml 里能使用的配置属性
  5. 可选, 写一个 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
)

注意:

  1. 配置文件到 POJO 的属性装配是要用到 setter 的, 所以要定义为 var, 如果定义为 val , starter 被引用后, 程序启动阶段在读取相应配置时, 如果文件配置与默认配置不一样的话会报错.
  2. 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
        )
    }
}

就是如此简单.

  1. @Configuration + @Bean 老组合了, 将一个类注册为 Spring Bean.
  2. @ConditionalOnClass, 是基于 @Conditional 的条件注解, 是 Spring4 提供的一种注解, 它的作用是按照设定的条件进行判断, 把满足判断条件的 Bean 注册到 Spring 容器. 相关注解如下:

 

 

  1. @EnableConfigurationProperties , 用于获取配置声明类, 原理不赘述.

spring.factories 文件

这个文件是上面写好的自动配置的入口, 有了它 Spring 才能读到上面写好的内容:

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

json 配置注释文件

可不写, 用来作为写属性时的提示.

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

代码

根据实际业务集成, 具体代码略.



Tags:SpringBootStarter   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
SpringBootStarter原理与应用
SpringBoot生态体系及扩展在应用程序开发过程中,很难通过一个框架就实现所有的功能需求,也不存在满足各种应用场景的统一开发框架。尤其近几年来,随着微服务、云原生等概念的落...【详细内容】
2023-08-21  Search: SpringBootStarter  点击:(359)  评论:(0)  加入收藏
保姆级,使用 KotlinScript 构建 SpringBootStarter
因业务需要, 公司内需要使用 SpringBoot Starter 构建 SDK. 不同的是使用了更为灵活的 Kotlin 语言, 构建脚本也换成了 Kotlin Script. 框架: SpringBoot 业务代码语言: Kot...【详细内容】
2022-09-27  Search: SpringBootStarter  点击:(333)  评论:(0)  加入收藏
▌简易百科推荐
Qt与Flutter:在跨平台UI框架中哪个更受欢迎?
在跨平台UI框架领域,Qt和Flutter是两个备受瞩目的选择。它们各自具有独特的优势,也各自有着广泛的应用场景。本文将对Qt和Flutter进行详细的比较,以探讨在跨平台UI框架中哪个更...【详细内容】
2024-04-12  刘长伟    Tags:UI框架   点击:(1)  评论:(0)  加入收藏
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(19)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(55)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(51)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(88)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条