既然能少写一半代码、还顺手灭掉 NPE,Spring 为什么不“更偏爱 Kotlin”一点呢?

举报
bug菌 发表于 2026/01/13 22:03:46 2026/01/13
【摘要】 🏆本文收录于《滚雪球学SpringBoot 3》:https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。  本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。...

🏆本文收录于《滚雪球学SpringBoot 3》:https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。
  
本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】https://blog.csdn.net/weixin_43970743/article/details/151115907,你想学习的都被收集在内,快速投入学习!!两不误。
  
若还想学习更多,可直接前往《滚雪球学SpringBoot(全版本合集)》:https://blog.csdn.net/weixin_43970743/category_11599389.html,涵盖SpringBoot所有版本教学文章。

演示环境说明:

  • 开发工具:IDEA 2021.3
  • JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
  • Spring Boot版本:3.5.4(于25年7月24日发布)
  • Maven版本:3.8.2 (或更高)
  • Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
  • 操作系统:Windows 11

前言:Kotlin 不是“更好看的 Java”,它是让你少犯错、少写废话的工具(而且 Spring 还真配合)

很多人对 Kotlin 的第一印象就是:
“哦,语法糖嘛,写起来短一点。”

但你真写一阵会发现:它的价值不止是短,更多是——把一堆原本要靠自律、靠 code review、靠祈祷才能守住的底线,直接塞进编译器里

Spring 官方文档也不藏着掖着:Spring Framework 的 Kotlin 文档里明确说,Spring Boot 是构建 Kotlin + Spring 应用最简单的方式,而且参考文档里很多示例同时给 Java 与 Kotlin。
Spring Boot 参考文档也单独开了一章讲 Kotlin 支持,说明这是“认真对待”的一等公民,而不是“顺便能跑”。

好,那我们就开拆。

1) 为什么 Spring 官方偏爱 Kotlin?

这句“偏爱”不是我瞎煽情,Spring 生态这些年确实在持续做 Kotlin 友好化:文档、扩展、DSL、空安全、协程适配……一整套。你可以从几个官方信号看出来。

1.1 官方文档把 Kotlin 当“默认选项之一”,不是彩蛋

  • Spring Framework Kotlin 总览里强调:用 Kotlin 构建 Spring 应用最简单的方式就是 Spring Boot,并引导去 start.spring.io 走 Kotlin 路线。
  • Spring Boot 直接有 “Kotlin Support” 章节,明确 Boot 是通过 Spring Framework、Spring Data、Reactor 等项目的支持来提供 Kotlin 体验。
  • Kotlin 官方文档也有专门的 Spring Boot 教程,把它当成“服务器端 Kotlin”主线之一。

这意味着:你不是在走野路子,你走的是“官方铺好的路”。

1.2 Kotlin 解决了 Spring 项目里最常见的两类痛点

痛点 A:样板代码太多

  • DTO、构造器、Getter/Setter、Builder、重载……这些 Kotlin 直接砍掉一大片(尤其 data class + 默认参数)。
  • 构造器注入在 Kotlin 里几乎“天然顺滑”,后面一节你会看到有多省心。

痛点 B:空指针与可空语义不清
Spring Framework 专门写了一章 Null-safety:Kotlin 的核心优势就是“编译期处理 null”,避免运行时撞上经典 NPE。
而 Spring 官方博客也提到,通过 JSpecify 等空性元数据的推进,Spring API 在 Kotlin 下可以变得更“惯用且空安全”。

一句话总结:Kotlin 把 Spring 开发里最磨人的两坨(废话 + NPE)都削薄了

2) 构造器注入的简化:主构造函数=最自然的依赖注入

如果你写过 Java 版 Spring,你一定见过这种“经典套路”:

  • 一堆 private final 字段
  • 一个构造器
  • 然后 IDE 帮你生成
  • 再然后你改了依赖,构造器参数顺序和字段又要调

Kotlin 在这件事上简直像开了挂:主构造函数直接就是你的依赖列表

2.1 Java 风格 vs Kotlin 风格(同一件事,Kotlin 少一半噪音)

Kotlin(推荐写法)

import org.springframework.stereotype.Service

@Service
class OrderService(
    private val pricingClient: PricingClient,
    private val inventoryClient: InventoryClient,
) {
    fun placeOrder(sku: String, qty: Int): String {
        val price = pricingClient.quote(sku)
        inventoryClient.reserve(sku, qty)
        return "OK price=$price"
    }
}

你看到了吗:

  • 依赖就是构造参数
  • 字段就是参数前面加 private val
  • 没有样板构造器,没有 Lombok,没有“为了注入而注入”的仪式感

小情绪:我以前写 Java 版构造器注入,经常有一种“我是在写业务,还是在写字段管理?”的迷惑感。Kotlin 这点是真的解脱。

2.2 “那我还需要 @Autowired 吗?”

通常不需要。Spring 的推荐方向本来就是构造器注入优先,而 Kotlin 写法本身就把构造器摆在脸上。
你会发现:依赖从“隐藏在字段注入里”变成“显式写在类签名里”,可读性直接上一个台阶。

3) Null Safety:消灭 NPE,不靠祈祷靠类型系统

Spring Framework 的 Null-safety 章节开头就点题:Kotlin 的 null-safety 能在编译期清晰处理 null,而不是运行时撞上 NullPointerException。

这句话很“官方”,我给你翻译成“写代码的人话”:

Kotlin 逼你在写代码的时候就想清楚:这个值到底可能为 null 吗?
不想清楚?对不起,编译不过。

3.1 一个最常见的 NPE 来源:Controller 入参 / 查询结果

你用 Java 时可能这么写(伪代码):

  • 请求参数缺失 → null
  • 查库没查到 → null
  • 然后你 .getXxx() → boom

Kotlin 的策略更“冷酷”:

  • String 表示“绝不为 null”
  • String? 表示“可能为 null”

于是很多坑会被你提前堵住。

3.2 Kotlin DTO:把“可空”写进类型里,代码审查更轻松

data class CreateUserRequest(
    val email: String,        // 必填
    val nickname: String?,    // 可选
)
  • 看到 String?,你不用猜它能不能为 null
  • 看到 String,你也不用猜它是不是“约定必填但可能空”

3.3 Spring API 的空空性信息:Kotlin 能吃到更多“空安全收益”

Spring 官方博客提到,随着 JSpecify 等元数据的使用推进,Spring API 在 Kotlin 下会更趋向“惯用的空安全”。
再结合 Spring Framework 的 Null-safety 文档,你会发现 Spring 对 Kotlin 的空性适配不是一句口号,是在持续补齐“类型语义”。

真实感受:当你开始依赖这些空性信息后,IDE 的提示会变得非常“唠叨”,但这种唠叨往往是在救命。

4) Kotlin Coroutines(协程)在 Controller 和 Repository 中的使用

协程这块特别容易被讲成玄学:
有人把它当“更好写的异步”,有人把它当“响应式替代品”,还有人一上来就 runBlocking 然后把系统堵死……

我们按 Spring 官方文档的语境来:Spring Framework 有专门的 Coroutines 章节,重点在于 挂起函数的执行上下文、与 Reactor 的适配、以及上下文传播(traceId/observation)

4.1 Controller:suspend fun 让接口代码像同步一样好读

这里我先给出最“顺滑”的用法:在支持协程的 Spring Web 栈里,Controller 方法可以写成 suspend fun,避免回调地狱,让异步逻辑更线性。Spring 的协程文档说明了 Spring 如何将协程挂起函数适配到 Reactor 类型,并提到上下文传播需要额外关注。

示例:协程 Controller(返回单个对象)

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

data class UserDto(val id: String, val name: String)

@RestController
class UserController(
    private val userService: UserService
) {
    @GetMapping("/users/{id}")
    suspend fun getUser(@PathVariable id: String): UserDto {
        // 看起来像同步,其实可以是非阻塞挂起
        return userService.findUser(id)
    }
}

Service:依然是 suspend,链路就很干净

import org.springframework.stereotype.Service

@Service
class UserService(
    private val userRepository: UserRepository
) {
    suspend fun findUser(id: String): UserDto {
        val user = userRepository.findById(id) ?: throw NoSuchElementException("user not found")
        return UserDto(user.id, user.name)
    }
}

你会发现:

  • 没有 Mono<UserDto> / Flux<UserDto> 的包装噪音
  • 错误处理可以用普通 try/catch 或抛异常
  • 读起来像同步,但可以保持“挂起即等待”的非阻塞语义(具体取决于你底层是否是真非阻塞)

4.2 Repository:Spring Data 的协程仓库(Coroutines Repository)

Spring Data Relational 的官方文档说得很直接:协程仓库建立在 reactive repositories 之上,通过 Kotlin 协程暴露非阻塞的数据访问,方法可以是 suspend 或返回 Flow

示例:CoroutineCrudRepository(R2DBC/Relational 场景常见)

import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import kotlinx.coroutines.flow.Flow

@Table("users")
data class User(
    @Id val id: String,
    val name: String
)

interface UserRepository : CoroutineCrudRepository<User, String> {
    suspend fun findByName(name: String): User?
    fun findAllByName(name: String): Flow<User>
}

然后 Service 里就能非常“线性”地写:

import kotlinx.coroutines.flow.toList
import org.springframework.stereotype.Service

@Service
class QueryService(private val repo: UserRepository) {

    suspend fun findOne(name: String): User =
        repo.findByName(name) ?: throw NoSuchElementException("not found")

    suspend fun findMany(name: String): List<User> =
        repo.findAllByName(name).toList()
}

4.3 一个必须提醒的坑:协程≠自动非阻塞,别把阻塞调用塞进协程里装无辜

协程能让你写得像同步,但底层是不是非阻塞,取决于你用的是什么:

  • 如果你的 Repository 是协程 + reactive 驱动(比如基于 reactive repository 的 coroutines repository),那链路才更像“真非阻塞”。
  • 如果你底层还是 JDBC(阻塞 I/O),你把它包成 suspend 也只是“语法上好看”,并不会凭空变快——最多是把阻塞藏得更深。

另外 Spring Framework 协程文档强调了上下文传播(例如 traceId)在挂起函数执行上下文中的问题,需要通过 Micrometer context-propagation 等机制配合。
这点在生产排障时非常要命:你以为日志会自动带 traceId,结果协程切走了上下文,日志突然“失忆”,那种感觉真的很想掀桌😇。

5) 一套“能直接起项目”的最小示例(Gradle Kotlin DSL)

你要是从零起步,Kotlin 官方就有 Spring Boot 教程入口,走官方路线最省心。
我这里给一个“常见组合”的 build.gradle.kts 轮廓(根据你用 WebFlux 还是 MVC、用 R2DBC 还是 JPA 再微调):

plugins {
    kotlin("jvm") version "2.0.0"
    kotlin("plugin.spring") version "2.0.0"
    id("org.springframework.boot") version "3.3.0"
    id("io.spring.dependency-management") version "1.1.6"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web") // 或 webflux
    implementation("org.springframework.boot:spring-boot-starter-data-r2dbc") // 协程+非阻塞更搭
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
}

你具体版本以你项目的 Spring Boot 版本线为准;如果你走 Spring 官方 Kotlin 指南,也会引导你从 start.spring.io 生成更匹配的依赖组合。

结语:Spring + Kotlin 的“合”,其实合在三件事上

如果让我用不那么鸡汤、但更真实的方式总结:

  1. 合在可读性:构造器注入 + data class + 默认参数,让“业务意图”更显眼
  2. 合在安全性:Null Safety 把一堆“隐性约定”变成“显式类型”
  3. 合在并发表达力:协程让异步逻辑更像人写的,但别忘了上下文传播与底层阻塞/非阻塞的边界

说白了就是:你少写废话、少靠运气、多靠编译器和框架给你兜住底线。
这感觉,就像从“手动挡爬坡”换成“自动挡+防溜坡”——不是你技术变差了,是你终于不用把精力浪费在不值钱的事上了😌。

🧧福利赠与你🧧

  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。

ps:本文涉及所有源代码,均已上传至Gitee:https://gitee.com/bugjun01/SpringBoot-demo 开源,供同学们一对一参考 Gitee传送门https://gitee.com/bugjun01/SpringBoot-demo,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗

🫵 Who am I?

我是 bug菌:

  • 热活跃于 CSDN:https://blog.csdn.net/weixin_43970743 | 掘金:https://juejin.cn/user/695333581765240 | InfoQ:https://www.infoq.cn/profile/4F581734D60B28/publish | 51CTO:https://blog.51cto.com/u_15700751 | 华为云:https://bbs.huaweicloud.cn/community/usersnew/id_1582617489455371 | 阿里云:https://developer.aliyun.com/profile/uolxikq5k3gke | 腾讯云:https://cloud.tencent.com/developer/user/10216480/articles 等技术社区;
  • CSDN 博客之星 Top30、华为云多年度十佳博主&卓越贡献奖、掘金多年度人气作者 Top40;
  • 掘金、InfoQ、51CTO 等平台签约及优质作者;
  • 全网粉丝累计 30w+

更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看:https://bbs.csdn.net/topics/612438251 👈️
硬核技术公众号 「猿圈奇妙屋」https://bbs.csdn.net/topics/612438251 期待你的加入,一起进阶、一起打怪升级。

- End -

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。