65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (3投票s)

2018 年 11 月 23 日

CPOL

6分钟阅读

viewsIcon

10604

使用 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

项目结构

从 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-noargapply 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/runners
  • POST - https://:8080/parkrun/runners
  • GET - https://:8080/parkrun/runners/2
  • DELETE - 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 以及集成测试用例有所帮助。编码愉快!

© . All rights reserved.