27、Kotlin与后端开发:Ktor框架入门、路由与中间件、数据序列化与反序列化、数据库操作
说实话,很多学Kotlin的朋友都问我一个问题:Kotlin到底能不能做后端?
我的回答很直接——不仅能做,而且做得相当漂亮。Ktor这个框架,就是JetBrains官方为Kotlin量身打造的后端解决方案。它轻量、异步、而且写起来特别有Kotlin的味道。
今天我们就来聊聊Ktor的核心玩法。我会带着你从零搭一个简单的后端服务,把路由、中间件、序列化、数据库操作这几个关键点都过一遍。
本章知识体系
Ktor框架 → 路由配置 → 中间件机制 → 数据序列化 → 数据库操作 → 完整实战
一、Ktor框架入门:从Hello World开始
Ktor的启动方式非常简洁。我个人习惯用Gradle来管理依赖,核心就两个东西:ktor-server-core和对应的引擎。
// build.gradle.kts
dependencies {
implementation("io.ktor:ktor-server-core:2.3.7")
implementation("io.ktor:ktor-server-netty:2.3.7")
implementation("io.ktor:ktor-server-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
implementation("org.jetbrains.exposed:exposed-core:0.41.1")
implementation("org.jetbrains.exposed:exposed-dao:0.41.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.41.1")
implementation("com.h2database:h2:2.2.224")
}
启动一个服务,其实就这几行代码:
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondText("Hello Ktor!")
}
}
}.start(wait = true)
}
嗯,这里要注意——embeddedServer是内嵌服务器,开发调试非常方便。生产环境你可以换成其他引擎,比如Tomcat或者Jetty。
二、路由配置:把请求分清楚
路由说白了就是URL分发器。你想想看,一个后端服务少说几十个接口,没有清晰的路由管理,代码很快就变成一锅粥。
routing {
// 用户相关
route("/user") {
get("/{id}") {
val userId = call.parameters["id"]
call.respondText("查询用户: $userId")
}
post("/create") {
// 创建用户逻辑
}
}
// 商品相关
route("/product") {
get("/list") {
call.respondText("商品列表")
}
}
}
我在项目中遇到过一个问题——路由层级太深,导致URL又长又难记。后来我建议团队把路由控制在两层以内,超过两层就考虑用子路由模块拆分。
小技巧:可以用route()函数做分组,这样代码结构清晰,也方便后期维护。
三、中间件:请求的守门员
中间件这个概念,说白了就是在请求到达你的业务逻辑之前,或者响应返回给客户端之前,插入一些处理逻辑。
Ktor的中间件机制叫"管道"(Pipeline)。你可以往管道里塞各种拦截器:日志、认证、CORS、请求限流等等。
fun Application.module() {
// 全局中间件:记录请求日志
install(CallLogging) {
level = Level.INFO
}
// 认证中间件
install(Authentication) {
bearer {
validate { credential ->
if (credential.token == "my-secret-token") {
UserIdPrincipal("admin")
} else {
null
}
}
}
}
routing {
authenticate {
get("/secure") {
call.respondText("这是安全接口")
}
}
}
}
我曾经踩过一个坑——中间件的安装顺序会影响执行结果。比如CORS中间件如果放在认证后面,跨域请求还没到认证就被拦截了。所以建议把CORS、日志这类通用中间件放在最前面。
注意:中间件的顺序很重要!先安装的先执行。如果你有多个中间件,一定要想清楚它们的执行顺序。
四、数据序列化与反序列化
后端开发免不了要和JSON打交道。Ktor官方推荐用kotlinx.serialization,用起来非常顺手。
// 定义数据类
@Serializable
data class User(
val id: Int,
val name: String,
val email: String
)
// 配置序列化
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
// 使用
routing {
get("/user/json") {
val user = User(1, "张三", "zhangsan@example.com")
call.respond(user) // 自动序列化为JSON
}
post("/user") {
val user = call.receive<User>() // 自动反序列化
call.respond(mapOf("status" to "ok"))
}
}
我个人习惯把ignoreUnknownKeys设为true。为什么呢?因为前后端联调时,前端多传一个字段是常有的事,设为true可以避免接口直接挂掉。
五、数据库操作:用Exposed操作数据库
Ktor本身不提供ORM,但和Exposed配合起来,简直天作之合。Exposed是JetBrains出品的Kotlin SQL框架,写起来非常Kotlin风格。
// 定义表结构
object Users : Table() {
val id = integer("id").autoIncrement()
val name = varchar("name", 50)
val email = varchar("email", 100)
override val primaryKey = PrimaryKey(id)
}
// 数据操作
class UserRepository {
fun getAll(): List<User> = transaction {
Users.selectAll().map {
User(
id = it[Users.id],
name = it[Users.name],
email = it[Users.email]
)
}
}
fun create(name: String, email: String): User = transaction {
val id = Users.insert {
it[Users.name] = name
it[Users.email] = email
} get Users.id
User(id, name, email)
}
}
这里有个坑——Exposed的transaction是阻塞的。在Ktor这种异步框架里,如果直接在主协程里调用,会阻塞线程。我建议用withContext切换到IO线程池:
suspend fun getAllAsync(): List<User> = withContext(Dispatchers.IO) {
transaction {
Users.selectAll().map { /* ... */ }
}
}
避坑指南:我曾经在生产环境遇到过数据库连接池耗尽的问题。原因是忘了给Exposed配置连接池大小。默认值太小,并发一高就挂了。建议用HikariCP做连接池管理。
六、完整实战:一个简单的用户管理API
把上面这些知识点串起来,就是一个完整的CRUD接口:
fun Application.module() {
install(ContentNegotiation) { json() }
install(CallLogging) { level = Level.INFO }
val repo = UserRepository()
routing {
get("/users") {
val users = repo.getAllAsync()
call.respond(users)
}
get("/users/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respondText("参数错误", status = HttpStatusCode.BadRequest)
return@get
}
val user = repo.getById(id)
if (user != null) call.respond(user)
else call.respondText("未找到", status = HttpStatusCode.NotFound)
}
post("/users") {
val user = call.receive<User>()
val created = repo.create(user.name, user.email)
call.respond(HttpStatusCode.Created, created)
}
}
}
你看,从框架启动到路由配置,从中间件到序列化,再到数据库操作,整个链路就打通了。Ktor的优势在于——它没有Spring那么重的容器,启动快、资源占用少,特别适合微服务和云原生场景。
当然,Ktor也不是万能的。如果你的项目需要大量第三方集成、或者团队习惯了Spring的生态,那还是用Spring Boot更稳妥。但如果你想要一个轻量、高效、纯Kotlin的后端方案,Ktor绝对值得一试。
我的建议:小项目、API网关、或者作为微服务的一部分,用Ktor非常合适。大而全的企业级应用,还是Spring Boot更成熟。
公众号:蓝海资料掘金营,微信deep3321