Kotlin: Spring Boot with Kotlin详解
最后更新:2026-08-26
Spring Boot + Kotlin 是后端微服务的黄金组合——Charlie 用 data class 做请求/响应体,用 控制器方法实现非阻塞 API,用扩展函数让代码更 Kotlin 惯用。
1. 你将学到
- Spring Boot + Kotlin 配置
- 控制器: + data class 请求/响应体
- 协程支持: 控制器方法
- JPA + Kotlin: 编译器插件
- Charlie 实战:OrderProcessor 微服务
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 请求处理链
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(构造器参数、可空性、默认值处理都有问题)。
📖 小节
- Spring Boot + Kotlin 配置需要 和 插件
- data class 做请求/响应 DTO,1 行替代 Java 30+ 行
- 控制器方法 + WebFlux 实现非阻塞 API
- 插件为 Entity 生成无参构造器
- 扩展函数 分离领域模型和 API 模型
- WebFlux + 协程吞吐量远超 MVC + 线程池
📝 作业
- 基础题(难度⭐):用 data class 定义 和 ,编写 Spring Boot 控制器。提示: +
- 进阶题(难度⭐⭐):实现 控制器方法,调用 Service 函数获取订单。提示:
- 挑战题(难度⭐⭐⭐):实现完整的 CRUD REST API + 协程 + R2DBC(或模拟 Repository),包含异常处理和验证。提示: +