ToolkitX
知识库工具箱

Kotlin + Spring Boot

Spring Boot 与 Kotlin 集成

25min·进阶

01. Spring Boot + Kotlin 项目搭建

Kotlin 和 Spring Boot 是绝配。用 Spring Initializr 选择 Kotlin 语言和 Gradle Kotlin DSL。Kotlin 的数据类天然适合 JPA 实体,data class User(...)。Controller 只需要注解加函数就能处理 HTTP 请求。空安全让 NullPointerException 在编译时就被揪出来。
kotlin
// build.gradle.kts
plugins {
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
    kotlin("jvm") version "1.9.21"
    kotlin("plugin.spring") version "1.9.21"
    kotlin("plugin.jpa") version "1.9.21"
}

// Application.kt
@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}
Kotlin + Spring 写起来比 Java 少 30%+ 的代码,主要是数据类和 null 安全省掉了大量模板。

02. Controller 和 RESTful API

@RestController 标记控制器,@GetMapping/@PostMapping 映射路由。Kotlin 的函数参数可以直接对应请求参数,@RequestParam 获取查询参数,@PathVariable 获取路径变量,@RequestBody 反序列化 JSON 为 Kotlin 数据类。响应自动序列化为 JSON。
kotlin
@RestController
@RequestMapping("/api/users")
class UserController(private val service: UserService) {
    
    @GetMapping
    fun list(@RequestParam(defaultValue = "1") page: Int): List<UserDto> {
        return service.findAll(page)
    }
    
    @GetMapping("/{id}")
    fun getById(@PathVariable id: Long): UserDto? {
        return service.findById(id)
    }
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun create(@Valid @RequestBody request: CreateUserRequest): UserDto {
        return service.create(request)
    }
    
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    fun delete(@PathVariable id: Long) {
        service.delete(id)
    }
}

data class CreateUserRequest(
    @field:NotBlank val name: String,
    @field:Email val email: String
)

data class UserDto(val id: Long, val name: String, val email: String)
Kotlin 数据类 + @Valid + Bean Validation 一行注解搞定参数校验。

03. JPA Repository 和事务

Spring Data JPA 让你几乎不用写 SQL。定义 interface 继承 JpaRepository<Entity, ID>,Spring 自动生成增删改查方法。方法名按命名规则写自动生成查询:findByName 查名字,findByAgeGreaterThan 查年龄大于。@Transactional 声明事务,@Modifying 标记更新删除操作。
kotlin
@Entity
@Table(name = "users")
class User(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    
    @Column(nullable = false, length = 50)
    var name: String,
    
    @Column(unique = true)
    var email: String
)

interface UserRepository : JpaRepository<User, Long> {
    fun findByName(name: String): User?
    fun findByEmailContaining(keyword: String): List<User>
    fun findByAgeGreaterThan(age: Int): List<User>
    
    @Query("SELECT u FROM User u WHERE u.createdAt > :since")
    fun findRecent(@Param("since") since: LocalDateTime): List<User>
    
    @Modifying
    @Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
    fun updateName(@Param("id") id: Long, @Param("name") name: String): Int
}

@Service
@Transactional
class UserService(private val userRepository: UserRepository) {
    fun findAll(): List<User> = userRepository.findAll()
    fun create(name: String, email: String) = userRepository.save(User(name = name, email = email))
}
Kotlin 的具名参数让 JPA 实体创建很自然:User(name = "小明", email = "[email protected]")。

04. 异常处理和全局异常拦截

用 @ControllerAdvice 做全局异常处理比在每个 controller 里写 try-catch 优雅得多。定义异常类和对应的处理函数,统一返回 ErrorResponse。@ExceptionHandler 按异常类型分发。service 层永远不需要知道 HTTP 相关的东西。
kotlin
// 自定义异常
class UserNotFoundException(id: Long) : RuntimeException("用户未找到: $id")
class EmailAlreadyUsedException(email: String) : RuntimeException("邮箱已注册: $email")

data class ErrorResponse(
    val code: Int,
    val message: String,
    val timestamp: LocalDateTime = LocalDateTime.now()
)

@RestControllerAdvice
class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException::class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    fun handleNotFound(ex: UserNotFoundException): ErrorResponse {
        return ErrorResponse(404, ex.message ?: "未找到")
    }
    
    @ExceptionHandler(EmailAlreadyUsedException::class)
    @ResponseStatus(HttpStatus.CONFLICT)
    fun handleConflict(ex: EmailAlreadyUsedException): ErrorResponse {
        return ErrorResponse(409, ex.message ?: "冲突")
    }
    
    @ExceptionHandler(MethodArgumentNotValidException::class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    fun handleValidation(ex: MethodArgumentNotValidException): ErrorResponse {
        val error = ex.bindingResult.fieldErrors.first().defaultMessage ?: "验证失败"
        return ErrorResponse(400, error)
    }
}
@RestControllerAdvice 是 Kotlin + Spring 项目的标配,让所有异常处理集中管理。

05. Kotlin 协程支持

Spring WebFlux 配合 Kotlin 协程让处理高并发请求变得简单。Controller 里用 suspend 函数替代返回 Mono/Flux。Repository 支持 CoroutineCrudRepository 返回挂起函数。一句话:加 kotlinx-coroutines-reactor 依赖,controller 的函数加 suspend 关键字就行。
kotlin
import kotlinx.coroutines.*

// 支持协程的 Repository
interface UserCoroutineRepository : CoroutineCrudRepository<User, Long> {
    suspend fun findByName(name: String): User?
}

@RestController
@RequestMapping("/api/users")
class UserReactiveController(
    private val repository: UserCoroutineRepository
) {
    @GetMapping
    suspend fun list(): List<User> = repository.findAll().toList()
    
    @GetMapping("/{id}")
    suspend fun getById(@PathVariable id: Long): User {
        return repository.findById(id) ?: throw UserNotFoundException(id)
    }
    
    @PostMapping
    suspend fun create(@RequestBody user: User): User = repository.save(user)
    
    // 并发请求多个数据源
    @GetMapping("/dashboard")
    suspend fun dashboard(): DashboardData = coroutineScope {
        val users = async { repository.findAll().toList() }
        val stats = async { fetchStatistics() }
        DashboardData(users.await(), stats.await())
    }
}
Kotlin 协程让 WebFlux 代码从 Reactive Stream 的复杂操作符地狱变回直觉的同步写法。

知识测验

1/4正确 0

Spring Boot 中 @RestController 的作用?