KotlinScript构建SpringBootStarter保姆级教程

目录
  • 引言
  • 一 如何用 Kotlin 写一个简单 SpringBoot Starter
    • 1 分析
    • 2 简单案例设计
    • 3 代码实现
      • 依赖项
      • 配置属性声明类: xxxProperties
      • 业务 Bean
      • 配置类: xxxAutoConfiguration
      • spring.factories 文件
      • json 配置注释文件
  • 二 进阶: 复杂配置参数的写法
  • 三 进阶: Starter 单元测试
    • 依赖
    • 配置文件
    • 测试类
  • 四 如何使用 Kotlin Script 构建成 Maven 依赖
  • 五 集成测试
    • 依赖
    • 配置文件

引言

因业务需要, 公司内需要使用 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
@ConditionalOnWebApplication web 应用才会实例化 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的资料请关注我们其它相关文章!

(0)

相关推荐

  • Kotlin 协程异步热数据流的设计与使用讲解

    目录 一.异步冷数据流 二.异步热数据流 1.异步热数据流的设计 1)SharedFlow接口 2)MutableSharedFlow接口 2.异步热数据流的使用 1)MutableSharedFlow方法 2)使用示例 一.异步冷数据流 在Kotlin协程:协程的基础与使用中,通过使用协程中提供的flow方法可以创建一个Flow对象.这种方法得到的Flow对象实际上是一个异步冷数据流,代码如下: private suspend fun test() { val flow = flow { em

  • Kotlin内存陷阱inline使用技巧示例详解

    目录 引言 错误示例 推荐示例 小结 总结 引言 inline ,翻译过来为 内联 ,在 Kotlin 中,一般建议用于 高阶函数 中,目的是用来弥补其运行时的 额外开销. 其原理也比较简单,在调用时将我们的代码移动到调用处使用,从而降低方法调用时的 栈帧 层级. 栈帧: 指的是虚拟机在进行方法调用和方法执行时的数据结构,每一个栈帧里都包含了相应的数据,比如 局部参数,操作数栈等等. Jvm在执行方法时,每执行一个方法会产生一个栈帧,随后将其保存到我们当前线程所对应的栈里,方法执行完毕时再将此方

  • Android开发Kotlin实现圆弧计步器示例详解

    目录 效果图 定义控件的样式 自定义StepView 绘制文本坐标 Android获取中线到基线距离 效果图 定义控件的样式 看完效果后,我们先定义控件的样式 <!-- 自定义View的名字 StepView --> <!-- name 属性名称 format 格式 string 文字 color 颜色 dimension 字体大小 integer 数字 reference 资源或者颜色 --> <declare-styleable name="StepView&q

  • Kotlin对象的懒加载方式by lazy 与 lateinit 异同详解

    目录 前言 lateinit by lazy 总结 前言 属性或对象的延时加载是我们相当常用的,一般我们都是使用 lateinit 和 by lazy 来实现. 他们两者都是延时初始化,那么在使用时那么他们两者有什么区别呢? lateinit 见名知意,延时初始化的标记.lateinit var可以让我们声明一个变量并且不用马上初始化,在我们需要的时候进行手动初始化即可. 如果我们不初始化会怎样? private lateinit var name: String findViewById<Bu

  • kotlin android extensions 插件实现示例详解

    目录 前言 原理浅析 总体结构 源码分析 插件入口 配置编译器插件传参 编译器插件接收参数 注册各种Extension IrGenerationExtension ExpressionCodegenExtension StorageComponentContainerContributor ClassBuilderInterceptorExtension PackageFragmentProviderExtension 总结 前言 kotlin-android-extensions 插件是 Ko

  • Android Kotlin全面详细类使用语法学习指南

    目录 前言 1. 类的声明 & 实例化 2. 构造函数 2.1 主构造函数 2.2 次构造函数 3. 类的属性 4. 可见性修饰符 5. 继承 & 重写 6. 特殊类 6.1 嵌套类(内部类) 6.2 接口 6.3 数据类 6.4 枚举类 总结 前言 Kotlin被Google官方认为是Android开发的一级编程语言 今天,我将主要讲解kotlin中的类的所有知识,主要内容包括如下: 1. 类的声明 & 实例化 // 格式 class 类名(参数名1:参数类型,参数名2:参数类型

  • Kotlin协程的基础与使用示例详解

    目录 一.协程概述 1.概念 2.特点 3.原理 1)续体传递 2)状态机 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 1)协程状态的转换 2)状态标识的变化 三.协程使用 1.协程的启动 1)runBlocking方法 2)launch方法 3)async方法 4)suspend关键字 5)withContext方法 6)suspend方法 2.协程间通信 1)Channel 2)Channel的容量 3)produce方法与act

  • KotlinScript构建SpringBootStarter保姆级教程

    目录 引言 一 如何用 Kotlin 写一个简单 SpringBoot Starter 1 分析 2 简单案例设计 3 代码实现 依赖项 配置属性声明类: xxxProperties 业务 Bean 配置类: xxxAutoConfiguration spring.factories 文件 json 配置注释文件 二 进阶: 复杂配置参数的写法 三 进阶: Starter 单元测试 依赖 配置文件 测试类 四 如何使用 Kotlin Script 构建成 Maven 依赖 五 集成测试 依赖 配

  • Android 创建依赖库的方法(保姆级教程)

    新建工程,新建Module 新建一个工程,之后按下图中的操作方式,创建一个 Module 创建 Android Library 选中 Android Library,之后点击Next! 输入你要创建module名字 输入你要创建module名字,如果有,但请不要删除前面的两个冒号(可能会因AndroidStudio版本原因显示界面不一样)!之后点击Finish! 在Module中新建测试类 如图,找到以下文件目录,在Module中新建测试类 上传github 之后要上传Github,导入JitP

  • 用Python简单实现个贪吃蛇小游戏(保姆级教程)

    一.前言 本期介绍 Python 练手级项目--贪吃蛇! 原本想推荐一个贪吃蛇的开源项目:python-console-snake,但由于该项目最近一次更新是 8 年前,而且在运行的时候出现了诸多问题.索性我就动手用 Python 重新写了一个贪吃蛇游戏. 下面我们就一起用 Python 实现一个简单有趣的命令行贪吃蛇小游戏,启动命令: git clone https://github.com/AnthonySun256/easy_games cd easy_games python snake

  • C语言文件操作零基础新手入门保姆级教程

    目录 一.前言 二.文件操作基础知识 ①什么是文件 ②数据文件类型 ③数据如何存储 ④如何读取二进制文件 ⑤什么是文件名 ⑥文件缓冲区 ⑦文件指针 三.文件操作函数 ①fopen 与 fclose ②fputc与fgetc ③fputs与fgets ④fprintf与fscanf ⑤fwrite与fread ⑥fseek与ftell与rewind ⑦ferror与feof ⑧补充函数 sscanf sprintf ⑨补充函数perror  strerror 总结 一.前言 我们如何使我们设计的程

  • 教你在VMware中安装Windows11操作系统的保姆级教程

    目录 1.Windows11操作系统简介 2.在VMware中创建一个新的虚拟机 2.1.下载Windows 11系统镜像 2.2.新建一个windows虚拟机 2.3.配置虚拟机资源设置 2.4.将虚拟机开机选择blos启动 3.安装Windows11操作系统 3.1.安装操作系统 3.2.设置系统 4.Windows11安装完成administrator 1.Windows11操作系统简介 Windows 11是由微软公司(Microsoft)开发的操作系统,应用于计算机和平板电脑等设备 [

  • 青龙面板拉库解决没有或丢失依赖can‘t find module的保姆级教程(附青龙面板脚本仓库)

    目录 1.青龙面板拉库 2.分享收集的青龙面板脚本仓库,建议选择拉一个就可以了 3.安装脚本所需要的依赖库 4.设置环境变量 5.拉取单个文件,依赖修复方法 如何拉库: 拉取单个脚本 使用方法: 拉库出现的问题及解决方案 解决办法: 方法1)复制对应缺失的<module名称>.js或.py到库文件夹 方法2)完善ql repo命令的依赖部分,重新运行拉库命令: 6.结尾 没有安装青龙面板的先看我另外一篇教程2022年青龙面板部署完整版教程(多图) 1.青龙面板拉库 先把配置文件config.s

  • 最新青龙面板2.10.2搭建+XDD-PLUS的保姆级教程

    目录 11月26日更新内容: 11月20日更新内容: 准备工作 安装青龙面板 安装go环境 安装XDD-Plus xdd-plus大佬更新了1.8版本已经更新了最新版的机器人了 更新方法: 整个xdd-plus目录删掉 然后按照教程重新安装一次 在启动之前先去qbot文件夹里面修改下config文件里面的QQ改成你机器人的QQ即可其他不用动 改完以后就./xdd扫码登录,挂后台完事 注:博主没有在用xdd了,xdd的配置文件记得在冒号有变留一个空格 如果有文字注释就在#号前面留一个空格. 11月

  • Kettle下载与安装保姆级教程(最新)

    目录 Kettle简介 主要功能 运行环境 Kettle下载 下载地址: 运行Kettle 导入数据库驱动jar包 Kettle使用 配置资源库与数据库 修改.添加用户信息 总结 Kettle简介 Kettle最早是一个开源的ETL(Extract-Transform-Load的缩写)工具,全称为KDE Extraction, Transportation, Transformation and Loading Environment.后来Kettle重命名为Pentaho Data Integ

  • 详解C语言随机数设置的三种方式(保姆级教程)

    目录 前言 随机数设置三板斧 第一式:rand函数 第二式:srand函数 第三式:time函数 前言 本篇文章将为大家介绍在C语言中如何设置随机数,在设置随机数的过程中,大家可能会遇到以下问题: 1.每次进入程序后的随机数与上一次相同. 2.当随机数设置过快时,可能会相同. 3.如何设置指定范围的随机数. 随机数设置三板斧 在设置随机数的时候,我们需要用到三个函数,它们分别是rand,time,srand.下面将一一进行讲解: 第一式:rand函数 我们可以打开MSDN去看看rand函数的定义

  • python tarfile压缩包操作保姆级教程

    目录 前言 1.tarfile 2.处理 open 后的 TarFile 对象 3.处理2中返回的 TarInfo 对象 4.示例 前言 上次写博客还在去年的8月底了,期间有了小宝,换工作等诸多事宜让我踩坑采的起飞,时隔4个月,逐渐找回状态.这篇的主题是python的第三方库zipfile,因工作中要处理大量的压缩包zip文件,所以趁着自由时间整理输出下,以提高下工作的愉悦感. 1.tarfile 这个工具可以帮我们解决 zip 压缩包的创建.读取.写入.添加.列出内部所有的成员.更高效的处理压

随机推荐