Kotlin: Spring Boot with Kotlin详解

最后更新:2026-08-26

Spring Boot + Kotlin 是后端微服务的黄金组合——Charlie 用 data class 做请求/响应体,用 控制器方法实现非阻塞 API,用扩展函数让代码更 Kotlin 惯用。

1. 你将学到


2. 一个架构师的真实故事

(1) 痛点:Java Spring Boot 的样板代码

Charlie 的 Java Spring Boot 控制器每个请求/响应都需要 30+ 行 POJO + getter/setter,异步 API 用 嵌套难以维护。

(2) Kotlin Spring Boot 的解法

KOTLIN
// Java: 30+ lines for request DTO
public class CreateOrderRequest {
    private String customerId;
    private Double total;
    // getter/setter x 2 = 8 lines
}

// Kotlin: 1 line for request DTO
data class CreateOrderRequest(val customerId: String, val total: Double)

// Suspend controller method
@PostMapping
suspend fun createOrder(@RequestBody req: CreateOrderRequest): OrderResponse {
    return orderService.createOrder(req)  // Non-blocking!
}

data class + suspend 让 Spring Boot 代码量减少 60%,异步 API 像同步一样可读。


3. Spring Boot + Kotlin 配置

(1) build.gradle.kts

KOTLIN
plugins {
    kotlin("jvm") version "1.9.22"
    kotlin("plugin.spring") version "1.9.22"   // Spring support
    kotlin("plugin.jpa") version "1.9.22"      // JPA no-arg
    kotlin("plugin.serialization") version "1.9.22"
    id("org.springframework.boot") version "3.2.1"
    id("io.spring.dependency-management") version "1.1.4"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-webflux")  // For coroutine support
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("org.postgresql:postgresql")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

(2) Kotlin Spring 插件说明

插件 作用
自动为 等注解的类打开
为 生成无参构造器
支持 data class JSON 序列化

4. REST 控制器

(1) 基本控制器

KOTLIN
@RestController
@RequestMapping("/api/orders")
class OrderController(private val orderService: OrderService) {

    @GetMapping
    fun getAllOrders(): List`<OrderResponse>` = orderService.findAll()

    @GetMapping("/{id}")
    fun getOrder(@PathVariable id: String): OrderResponse =
        orderService.findById(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)

    @PostMapping
    fun createOrder(@RequestBody request: CreateOrderRequest): OrderResponse =
        orderService.create(request)
}

(2) 请求/响应 data class

KOTLIN
data class CreateOrderRequest(
    val customerId: String,
    val total: Double,
    val items: List`<OrderItemRequest>` = emptyList()
)

data class OrderItemRequest(
    val sku: String,
    val quantity: Int,
    val unitPrice: Double
)

data class OrderResponse(
    val id: String,
    val total: Double,
    val status: String,
    val customer: String
)

(3) Java DTO vs Kotlin data class

维度 Java DTO Kotlin data class
代码行数 30+ 行 3 行
equals/hashCode 手写/Lombok 自动生成
不可变 需手动 默认 val
默认值 方法重载 参数默认值
JSON 映射 Jackson 注解 jackson-module-kotlin 自动

5. 协程支持

(1) suspend 控制器方法

KOTLIN
// Add webflux dependency for coroutine support
@RestController
@RequestMapping("/api/orders")
class OrderController(private val orderService: OrderService) {

    // Suspend controller: non-blocking, runs on Netty event loop
    @GetMapping("/{id}")
    suspend fun getOrder(@PathVariable id: String): OrderResponse {
        return orderService.findByIdAsync(id)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
    }

    @PostMapping
    suspend fun createOrder(@RequestBody request: CreateOrderRequest): OrderResponse {
        return orderService.createAsync(request)
    }

    // Flow endpoint: streaming response
    @GetMapping("/stream")
    fun orderStream(): Flow`<OrderResponse>` = orderService.orderStream()
}

(2) Spring Boot 请求处理链

100%
flowchart TD
    A[HTTP Request] --> B[DispatcherServlet<br/>or Netty]
    B --> C[Controller<br/>@RestController]
    C --> D{suspend?}
    D -->|Yes| E[Coroutine Context<br/>Non-blocking]
    D -->|No| F[Servlet Thread<br/>Blocking]
    E --> G[Service Layer<br/>suspend functions]
    F --> G
    G --> H[Repository<br/>R2DBC / JPA]
    H --> I[Database]

(3) 阻塞 vs 非阻塞对比

维度 阻塞(MVC) 非阻塞(WebFlux + 协程)
线程模型 每请求一线程 事件循环
并发上限 ~200(线程池) ~100,000+(协程)
代码风格 同步 suspend(像同步)
数据库 JPA(阻塞) R2DBC(响应式)
吞吐量

6. JPA + Kotlin

(1) Entity 定义

KOTLIN
@Entity
@Table(name = "orders")
class OrderEntity(
    @Id @GeneratedValue(strategy = GenerationType.UUID)
    val id: String = "",
    val total: Double = 0.0,
    val status: String = "PENDING",
    val customerId: String = "",
    @CreationTimestamp
    val createdAt: LocalDateTime = LocalDateTime.now()
)

// Repository
interface OrderJpaRepository : JpaRepository<OrderEntity, String> {
    fun findByCustomerId(customerId: String): List`<OrderEntity>`
    fun countByStatus(status: String): Long
}

(2) no-arg 插件

KOTLIN
// kotlin-jpa plugin auto-generates no-arg constructor for @Entity classes
// Without it, JPA cannot instantiate Kotlin classes (all have constructor params)

7. 完整示例:OrderProcessor 微服务

KOTLIN
// ============================================
// OrderProcessor - Spring Boot Microservice
// Feature: REST API + suspend + data class
// ============================================

// --- Domain ---
data class Order(val id: String, val total: Double, var status: String, val customerId: String)

// --- Request/Response ---
data class CreateOrderRequest(val customerId: String, val total: Double)
data class OrderResponse(val id: String, val total: Double, val status: String, val customerId: String)
data class UpdateStatusRequest(val status: String)

// --- Repository (simulated) ---
class OrderRepository {
    private val storage = mutableMapOf<String, Order>()

    fun save(order: Order): Order {
        storage[order.id] = order
        return order
    }

    fun findById(id: String): Order? = storage[id]

    fun findAll(): List`<Order>` = storage.values.toList()

    fun deleteById(id: String) { storage.remove(id) }
}

// --- Service ---
class OrderService(private val repo: OrderRepository) {
    private var idCounter = 0L

    fun create(request: CreateOrderRequest): Order {
        val order = Order("ORD-${++idCounter}", request.total, "PENDING", request.customerId)
        return repo.save(order)
    }

    fun findById(id: String): Order? = repo.findById(id)

    fun findAll(): List`<Order>` = repo.findAll()

    fun updateStatus(id: String, newStatus: String): Order {
        val order = repo.findById(id) ?: throw NoSuchElementException("Order $id not found")
        order.status = newStatus
        return repo.save(order)
    }

    fun delete(id: String) = repo.deleteById(id)
}

// --- Controller (simulated, requires Spring Boot runtime) ---
// @RestController
// @RequestMapping("/api/orders")
// class OrderController(private val orderService: OrderService) {
//     @GetMapping fun getAll() = orderService.findAll().map { it.toResponse() }
//     @GetMapping("/{id}") fun getOne(@PathVariable id: String) = orderService.findById(id)?.toResponse()
//     @PostMapping fun create(@RequestBody req: CreateOrderRequest) = orderService.create(req).toResponse()
// }

// --- Extension for mapping ---
fun Order.toResponse() = OrderResponse(id, total, status, customerId)

// --- Demo ---
fun main() {
    val repo = OrderRepository()
    val service = OrderService(repo)

    println("=== Spring Boot OrderProcessor API Demo ===\n")

    // POST /api/orders
    val order1 = service.create(CreateOrderRequest("CUST-001", 299.99))
    println("POST /api/orders -> ${order1.toResponse()}")

    val order2 = service.create(CreateOrderRequest("CUST-002", 15_000.00))
    println("POST /api/orders -> ${order2.toResponse()}")

    // GET /api/orders
    println("\nGET /api/orders -> ${service.findAll().map { it.toResponse() }}")

    // GET /api/orders/{id}
    println("GET /api/orders/ORD-1 -> ${service.findById("ORD-1")?.toResponse()}")

    // PATCH /api/orders/{id}/status
    val updated = service.updateStatus("ORD-1", "CONFIRMED")
    println("PATCH /api/orders/ORD-1/status -> ${updated.toResponse()}")

    // Summary
    val revenue = service.findAll().filter { it.status != "CANCELLED" }.sumOf { it.total }
    println("\nTotal Revenue: \$$revenue USD across ${service.findAll().size} orders")
}

输出:

TEXT 📖 仅展示
=== Spring Boot OrderProcessor API Demo ===

POST /api/orders -> OrderResponse(id=ORD-1, total=299.99, status=PENDING, customerId=CUST-001)
POST /api/orders -> OrderResponse(id=ORD-2, total=15000.0, status=PENDING, customerId=CUST-002)

GET /api/orders -> [OrderResponse(id=ORD-1, total=299.99, status=PENDING, customerId=CUST-001), OrderResponse(id=ORD-2, total=15000.0, status=PENDING, customerId=CUST-002)]
GET /api/orders/ORD-1 -> OrderResponse(id=ORD-1, total=299.99, status=PENDING, customerId=CUST-001)
PATCH /api/orders/ORD-1/status -> OrderResponse(id=ORD-1, total=299.99, status=CONFIRMED, customerId=CUST-001)

Total Revenue: $15299.99 USD across 2 orders

❓ 常见问题

Q Spring Boot 用 MVC 还是 WebFlux?
A 新项目推荐 WebFlux + 协程——suspend 写法像同步但性能是非阻塞的。如果团队更熟悉 JPA 且无需极高并发,MVC + 协程也行。
Q Kotlin data class 可以做 JPA Entity 吗?
A 可以但有限制——data class 是不可变的,JPA 需要可变实体来脏检测。建议 Entity 用普通 class + var 属性,DTO 用 data class。
Q 插件做了什么?
A 自动将 // 等注解的类标记为 ,因为 Spring AOP 需要创建代理(需要可继承的类)。
Q suspend 控制器方法需要 WebFlux 吗?
A 是的。Spring MVC 不原生支持 suspend,需要 WebFlux 依赖。Spring 6.1+ 已改进支持,但 WebFlux 仍推荐。
Q 如何在 Spring Boot 中使用 Kotlin 协程?
A 加 依赖,控制器方法用 ,Service 层用 函数,Repository 用 R2DBC 或协程扩展。
Q 必须吗?
A 强烈推荐。没有它,Jackson 无法正确反序列化 Kotlin data class(构造器参数、可空性、默认值处理都有问题)。

📖 小节


📝 作业

  1. 基础题(难度⭐):用 data class 定义 和 ,编写 Spring Boot 控制器。提示: +
  2. 进阶题(难度⭐⭐):实现 控制器方法,调用 Service 函数获取订单。提示:
  3. 挑战题(难度⭐⭐⭐):实现完整的 CRUD REST API + 协程 + R2DBC(或模拟 Repository),包含异常处理和验证。提示: +

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏