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 的作用?