Kotlin:如何使用 Spring Boot、Spring Data 和 H2 DB 实现 REST API






4.80/5 (3投票s)
使用 Kotlin 和 SpringBoot 开发 REST API
引言
在本文中,我们将讨论 Kotlin。我使用 Spring Boot、Spring Data 和 H2 内存数据库在 Kotlin 中开发了一个非常简单的 REST API。
Kotlin 和 Spring Boot 配合得很好。
您会在代码演练部分注意到,项目中没有控制器(controller)也没有服务(service)类。这是 Spring 的 @RepositoryRestResource
的魔法,下面将进一步解释。
我对 Kotlin 没有经验,但我在 GitHub 项目中看到和阅读的 Kotlin 代码绝对值得探索。
您可能会问一个重要的问题:为什么选择 Kotlin?
为什么选择 Kotlin?
- Kotlin 编译为字节码,因此它的性能与 Java 一样好。
- Kotlin 比 Java 更简洁。
- Kotlin 的
Data
类比 Java 的value
类更简洁。 - 类默认是 final 的,这对应于《Effective Java》第 17 条——如果您想让一个类可继承,则需要显式地加上 open 。
- 抽象类默认是 open 的。
- Kotlin 的一个关键特性是 null 安全,它可以在编译时优雅地处理 null 值,而不是在运行时遇到著名的
NullPointerException
。 - 主构造函数 vs. 辅助构造函数——如果您需要一个以上的构造函数,那么您才会选择辅助构造函数。否则,大多数 Kotlin 类都会有一个主构造函数。
- Kotlin 也可以用作脚本语言。
- Kotlin 和 Java 是可互操作的,因此很容易在代码库的一小部分上试用 Kotlin。
- Kotlin 使用激进的类型推断来确定未声明类型的变量和表达式的类型。这降低了语言相对于 Java 的冗余度。
- Google 完全支持 Kotlin 用于其 Android 操作系统。
以下两点引用自 Wikipedia
- “根据 JetBrains 的博客,Amazon Web Services、Pinterest、Coursera、Netflix、Uber 等公司都在使用 Kotlin。Corda 是一种由知名银行(如高盛、富国银行、摩根大通、德意志银行、瑞银、汇丰银行、法国兴业银行、法国农业信贷银行)组成的联盟开发的分布式账本,其代码库中有超过 90% 是 Kotlin。”
- 根据 Google 的说法,Expedia、Flipboard、Pinterest、Square 等多家主要开发者已经在其 Android 生产应用中采用了 Kotlin。
代码演练
本文示例的项目代码可以在我的 Kotlin Github 仓库 中找到,其中演示了一个使用 Kotlin 和 Spring Boot 的简单 REST API。
克隆 - https://github.com/BeTheCodeWithYou/SpringBoot-Kotlin.git
- Kotlin
- Spring Boot
- Spring Data
- H2 内存数据库
- Gradle
- ZeroCode 集成测试框架
项目结构
从 IDE 运行应用程序
直接从 IDE 运行集成测试
理解 build.gradle
buildscript {
ext {
kotlinVersion = '1.2.41'
springBootVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}")
}
}
apply plugin: 'base'
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'kotlin-jpa'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.xp.springboot.restapi.kotlin'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-web')
compile('com.fasterxml.jackson.module:jackson-module-kotlin')
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compile("org.jetbrains.kotlin:kotlin-reflect")
runtime("com.h2database:h2")
testCompile("org.jsmart:zerocode-rest-bdd:1.2.11")
testCompile('org.springframework.boot:spring-boot-starter-test')
}
task integrationTests (type: Test) {
delete '/target/'
systemProperty 'zerocode.junit', 'gen-smart-charts-csv-reports'
include 'integrationtests/TestGetOperations.class'
include 'integrationtests/TestPatchOperations.class'
include 'integrationtests/TestPostOperations.class'
testLogging {
showStandardStreams = true
}
}
org.jetbrains.kotlin:kotlin-gradle-plugin
用于编译 Kotlin 源代码和模块。org.jetbrains.kotlin:kotlin-allopen
— 这是这里有趣的部分。在 Kotlin 中,默认情况下,所有类都是final
的。
现在,为了使一个类可继承,您必须用 open
关键字进行注解。问题是,许多其他库,如 Spring、测试库(Mockito 等),要求类和方法变成非 final
的。在 Spring 中,这类类主要包括 @Configuration
类和 @Bean
方法。
这里的规则很简单;您需要注解 @Configuration
和 @Bean
方法来将它们标记为 open,但这种方法很繁琐且容易出错,因此 Kotlin 推出了编译器插件,通过此依赖项使用 org.jetbrains.kotlin:kotlin-noarg
和 apply plugin: 'kotlin-jpa'
来自动化此过程。
为了能够使用 Kotlin 的不可变类,我们需要启用 Kotlin JPA 插件。它将为任何用 @Entity
注解的类生成无参构造函数,apply plugin: 'kotlin'.
为了定位 JVM,需要应用 Kotlin 插件:apply plugin: 'kotlin-spring'
。这对于 Spring Kotlin 集成是必需的。
testCompile("org.jsmart:zerocode-rest-bdd:1.2.11")
- 集成测试库依赖。
编译器选项
Spring 的 nullability 注解为 Kotlin 开发者提供了整个 Spring API 的 null
安全性,其优点是在编译时处理与 null
相关的问,这可以通过添加 -Xjsr305
编译器标志和 strict 选项来启用。它还配置 Kotlin 编译器生成 Java 8 字节码。
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
是 Kotlin 标准库的 Java 8 版本。compile('com.fasterxml.jackson.module:jackson-module-kotlin')
增加了对 Kotlin 和数据类序列化/反序列化的支持。compile("org.jetbrains.kotlin:kotlin-reflect")
是 Kotlin 反射库。
Spring Boot Gradle 插件会自动使用 Kotlin Gradle 插件声明的 Kotlin 版本,因此版本未在依赖项部分中显式定义。
所有剩余的条目都是不言自明的。
Kotlin 代码
Spring Boot 应用程序
/src/main/kotlin/com.xp.springboot.kotlin.SpringBootKotlinRestApiApplication.kt
请注意上面缺少的分号。只有当您有 @Bean
时,才需要打开和关闭类的大括号。否则,需要一个带有 name
的类。Runapplication
是一个顶级函数。
package com.xp.springboot.kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.context.annotation.Bean
import com.xp.springboot.kotlin.repository.ParkRunnerRepository
import org.springframework.boot.CommandLineRunner
import com.xp.springboot.kotlin.model.ParkRunner
import org.springframework.boot.ApplicationRunner
import org.springframework.boot.SpringApplication
import org.springframework.boot.context.ApplicationPidFileWriter
@SpringBootApplication
@ComponentScan(basePackages = arrayOf("com.xp.springboot.*"))
@EnableJpaRepositories("com.xp.springboot.*")
@EntityScan("com.xp.springboot.*")
class SpringBootKotlinRestApiApplication {
@Bean
fun run(repository : ParkRunnerRepository) = ApplicationRunner {
repository.save(ParkRunner(firstName = "NEERAJ", lastName="SIDHAYE", gender="M",
totalRuns="170", runningClub="RUNWAY"))
}
}
fun main(args: Array<String>) {
runApplication<SpringBootKotlinRestApiApplication>(*args)
}
fun start() {
runApplication<SpringBootKotlinRestApiApplication>()
}
创建数据类
然后,我们使用 Kotlin 数据类创建模型,数据类用于保存数据,并自动提供 equals()
、hashCode()
、toString()
、componentN()
函数和 copy()
。
此外,您可以在同一个数据类中定义多个实体。Var
类似于通用变量,在 Kotlin 中称为可变变量,可以多次赋值。
还有另一种类型:val
,它类似于常量变量,在 Kotlin 中称为不可变的,只能初始化一次。此外,val
是只读的,您不允许显式写入 val
。
package com.xp.springboot.kotlin.model
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table
@Entity
@Table(name="PARK_RUNNER")
data class ParkRunner (
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var parkRunId: Long = -1,
@Column(name = "firstName")
var firstName: String = "",
@Column(name = "lastName")
var lastName: String = "",
@Column(name = "gender")
var gender: String = "",
@Column(name = "runningClub")
var runningClub: String = "",
@Column(name = "totalRuns")
var totalRuns: String = "0"
){ }
创建存储库
是的,只需一行代码即可使用 Spring Data curd 存储库定义存储库接口。这里有趣的 Spring 注解是 RepositoryRestResource
。通过添加 spring-boot-starter-data-rest
依赖项即可获得此功能。
package com.xp.springboot.kotlin.repository
import org.springframework.data.repository.CrudRepository
import com.xp.springboot.kotlin.model.ParkRunner
import org.springframework.data.rest.core.annotation.RepositoryRestResource
@RepositoryRestResource(collectionResourceRel = "runners", path = "runners")
interface ParkRunnerRepository : CrudRepository <ParkRunner, Long >{
}
如果您注意到,没有 Controller,也没有 Service。本项目通过 HATEOAS 启用了以下 REST 端点。
GET
- https://:8080/parkrun/runnersPOST
- https://:8080/parkrun/runnersGET
- https://:8080/parkrun/runners/2DELETE
- https://:8080/parkrun/runners/1
这是 @RepositoryRestResource
的魔法。
应用 collectionResourceRel
来定义自定义资源标签,否则,注解将使用模型类名(/parkRunners
)的默认值。
在运行时,Spring Data REST 会自动创建此接口的实现。然后,它将使用 @RepositoryRestResource
注解来指导 Spring MVC 并在 /parkRunners
创建 RESTful 端点。
集成测试用例
GET 操作测试
package integrationtests
import org.jsmart.zerocode.core.domain.TargetEnv
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner
import org.junit.runner.RunWith
import org.junit.Test
import org.jsmart.zerocode.core.domain.JsonTestCase
import org.jsmart.zerocode.core.domain.EnvProperty
@TargetEnv("application_host.properties")
@RunWith(ZeroCodeUnitRunner::class)
class TestGetOperations {
@Test
@JsonTestCase("integration_tests/get/get_all_runners.json")
fun `get all runners`() {
}
}
POST 操作测试
package integrationtests
import org.jsmart.zerocode.core.domain.JsonTestCase
import org.jsmart.zerocode.core.domain.TargetEnv
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner
import org.junit.runner.RunWith
import org.junit.Test
@TargetEnv("application_host.properties")
@RunWith(ZeroCodeUnitRunner::class)
class TestPostOperations {
@Test
@JsonTestCase("integration_tests/post/create_new_runner.json")
fun `post create runner`() {
}
}
PATCH 操作测试
package integrationtests
import org.junit.runner.RunWith
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner
import org.jsmart.zerocode.core.domain.TargetEnv
import org.jsmart.zerocode.core.domain.JsonTestCase
import org.junit.Test
@TargetEnv("application_host.properties")
@RunWith(ZeroCodeUnitRunner::class)
class TestPatchOperations {
@Test
@JsonTestCase("integration_tests/patch/patch_runner_profile.json")
fun `patch runner profile`() {
}
}
构建应用程序
gradle clean build -x test
这将在 /build/libs/ 目录下生成 spring boot 应用程序 jar。
通过 -x test
跳过集成测试,因为集成测试需要应用程序正在运行,所以我们首先只构建应用程序,然后运行应用程序,然后针对本地运行的应用程序实例执行集成测试。
运行应用程序
Gradle clean build 后,当 jar 准备就绪后,使用以下命令运行应用程序:
java -jar SpringBootKotlinRestAPI-0.0.1-SNAPSHOT.jar
并在浏览器中直接访问 URL https://:8080/parkrun
。
这是响应的样子
{
"_links": {
"runners": {
"href": "https://:8080/parkrun/runners"
},
"profile": {
"href": "https://:8080/parkrun/profile"
}
}
}
探索此简单 Kotlin REST API 提供的其他端点
GET - https://:8080/parkrun/runners
POST - https://:8080/parkrun/runners
GET - https://:8080/parkrun/runners/2
>PATCH - https://:8080/parkrun/runners/1
运行集成测试用例
gradle integrationTests
此 Gradle 任务将针对本地运行的应用程序 jar 实例运行所有集成测试。
集成测试报告
您可以在 /target/ 文件夹下找到集成测试和日志。
希望您发现本文对于使用 Kotlin 开发 Spring Boot REST API 以及集成测试用例有所帮助。编码愉快!