「我有 10 年的 Spring Boot 經驗,整合個 Compose Desktop 能有多難?」
——翻車前一小時的我
昨天的選型會議上,我信心滿滿地選擇了 Kotlin Multiplatform + Compose Desktop + Spring AI 的技術組合。
理由很充分。Kotlin 統一前後端,減少語言切換成本。Compose Desktop 提供原生 macOS 體驗。Spring Boot 是我的老朋友,閉著眼睛都能寫。Spring AI 提供了完整的 AI 模型整合。
看起來完美無缺,對吧?
既然都是 JVM 生態,為什麼不把 Spring Boot 和 Compose Desktop 放在同一個 JVM 裡運行呢?
這樣可以減少記憶體佔用,只有一個 JVM。前後端直接方法調用,沒有 HTTP 開銷。部署簡單,只有一個執行檔。我甚至畫了一個「優雅」的架構圖:
┌─────────────────────────────────┐
│ Single JVM Process │
├─────────────────────────────────┤
│ Compose Desktop (UI) │
│ ↓ │
│ Direct Method Call │
│ ↓ │
│ Spring Boot (Backend) │
│ ↓ │
│ SQLite DB │
└─────────────────────────────────┘
看起來很美好。
第一個問題來得比預期還快。
當我寫下 main 函數時,愣住了。Spring Boot 說「我要這樣啟動」,Compose Desktop 說「不行,我要這樣」。兩個框架都要控制 main 函數,都要管理應用程式生命週期。
// Spring Boot 說:我要這樣啟動
fun main(args: Array<String>) {
runApplication<BackendApplication>(*args)
}
// Compose Desktop 說:不行,我要這樣
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
// 我:???
這就像是兩個 CEO 在同一家公司。誰聽誰的?
好不容易寫了個 SpringManager 來協調兩者。
object SpringManager {
fun start() {
springThread = Thread {
context = SpringApplicationBuilder(BackendApplication::class.java)
.web(WebApplicationType.NONE) // 不要 Web 服務器
.headless(false) // 我們有 UI
.run()
}
// 等 Spring 啟動完成
while (context == null || !context!!.isRunning) {
Thread.sleep(100) // 這行程式碼後來讓我後悔萬分
}
}
}
然後在 Compose 中調用。結果呢?
有時候 Spring 啟動太慢,UI 已經在訪問服務,NullPointerException。有時候等待邏輯死循環,整個應用程式卡死。關閉視窗時,Spring Context 不知道該不該關,資源洩漏警告滿天飛。
Spring Boot 作為一個 server-side 框架,它問我:「PostgreSQL 在哪?MySQL 在哪?Redis 在哪?」
我:「不好意思,這是個桌面應用,我們用 SQLite...」
Spring Data JPA:「SQLite?那是什麼?我只認識企業級資料庫!」
於是我被迫用 H2 Database 做妥協。但 H2 不是為生產環境設計的,而且 Compose 端用的是 SQLDelight + SQLite。現在有兩套資料層,兩套資料模型、兩種查詢語言、兩個事務管理器。
// Compose 端:優雅的 SQLDelight
val database = JdbcSqliteDriver("jdbc:sqlite:$databasePath")
// Spring 端:彆扭的 JPA
@Entity
class Project {
@Id @GeneratedValue
var id: Long? = null
}
我在寫什麼?精神分裂應用程式?
當應用程式崩潰時,錯誤堆疊是這樣的:
Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException
at org.springframework.boot.SpringApplication.run
at compose.desktop.ComposeLayer.invoke
at org.springframework.context.annotation.ConfigurationClassParser
at androidx.compose.runtime.internal.ComposableLambda
at kotlin.coroutines.intrinsics.IntrinsicsKt
at org.springframework.beans.factory.support.AbstractBeanFactory
at androidx.compose.ui.platform.DesktopOwner
... 200 more lines
這是 Spring 的問題?Compose 的問題?還是我的問題?
答案:都是。
經過 48 小時的奮戰,無數杯咖啡,以及差點砸爛鍵盤的衝動後,我終於明白了一個道理。
框架不是樂高積木,不能隨便拼在一起。
每個框架都有自己的設計哲學。Spring Boot 認為自己是宇宙的中心,要控制一切。Compose Desktop 認為 UI 才是王道,其他都是配角。我天真地以為可以讓它們和平共處。
這就像讓梅西和 C 羅在同一支球隊踢同一個位置。理論上很美好,實際上... 你懂的。
當我終於放棄這個「巧妙」的架構,回頭看 AI 助手(Gemini)最初的建議時,發現它一開始就建議用分離進程架構。
它甚至委婉地警告過:「將 Spring Boot 和 Compose Desktop 整合在同一個 JVM 中可能會遇到一些挑戰...」
「一些挑戰」...
這是我見過最保守的描述了。
經過這次慘痛的教訓,我開始意識到問題不只是技術整合那麼簡單。
更深層的問題是:我一直在用 Java/Spring 的思維寫 Kotlin 程式碼。
這就像用英文文法說中文,表面上能溝通,但總是怪怪的。Kotlin 不只是「更好的 Java」,它有自己的哲學和最佳實踐。
明天,我得好好反思一下,重新學習 Kotlin 的思維方式。一人公司的架構師,不只要懂技術,還要懂得何時該放下固執,擁抱新的思維。
現在,就讓我們為這次壯烈的翻車默哀三秒鐘吧。
「經驗有時候是最大的陷阱,它讓你以為熟悉的道路永遠正確。」
關於作者:Sam,一人公司創辦人。正在打造 Grimo,一個智能任務管理和分配平台。
專案連結:GitHub - grimostudio