為 Spring Boot 應用提供完整的 MetaMessage 協議支持。
MetaMessage 是一種結構化數據交換協議,自描述、自約束、自示例,實現了無損數據交換。本項目讓 Spring Boot 應用能以零配置接入 MetaMessage 生態,並提供類型安全的泛型 HTTP 客戶端。
- 組合映射註解 ——
@MMGet/@MMPost/@MMPut/@MMDelete/@MMPatch自動設置consumes/produces @MMBody/@MMQuery——@MMBody替代@RequestBody解碼請求體,@MMQuery從?data=<hex>解碼查詢參數- Schema 自動推斷 —— 無需顯式
@MMSchema,從@MMBody/@MMQuery參數自動推斷 OPTIONS 樣例類型 - 零配置自動裝配 —— 引入 starter 即生效,可通過
metamessage.*屬性按需關閉各模塊 - OPTIONS Schema 發現 —— OPTIONS 請求返回結構體樣例 +
Schema-Md5校驗頭 - 泛型 HTTP 客戶端 ——
mmGet/mmPost/mmPut/mmDelete/mmPatchreified 函數,自動 OPTIONS 預檢 + Schema-Md5 緩存 - 完整示例 —— 內存 CRUD 演示服務端 + 客戶端全流程
mm-spring-boot/
├── settings.gradle.kts # 根 settings,聲明三個子模塊
├── build.gradle.kts # 根 build,統一 group/version/倉庫
├── mm-spring-boot-starter/ # 服務端 starter
│ ├── build.gradle.kts
│ └── src/main/kotlin/io/github/metamessage/spring/boot/
│ ├── codec/ # MetaMessageCodec + 異常封裝
│ ├── converter/ # wire + JSONC HttpMessageConverter
│ ├── binder/ # @MMBody/@MMQuery + ArgumentResolver + ConverterFactory
│ ├── web/ # @MMGet/@MMPost/@MMPut/@MMDelete/@MMPatch
│ ├── autoconfigure/ # @AutoConfiguration + Properties
│ └── schema/ # @MMSchema + OPTIONS 攔截器(支持自動推斷)
├── mm-client/ # 泛型 HTTP 客戶端
│ ├── build.gradle.kts
│ └── src/main/kotlin/io/github/metamessage/client/
│ ├── MMClient.kt # 核心 doRequest<REQ, RESP>
│ └── MMClients.kt # mmGet/mmPost/... 便捷函數
└── examples/ # 可運行的示例應用
├── build.gradle.kts
└── src/main/kotlin/com/example/
├── Application.kt # Spring Boot 入口
├── model/Models.kt # @MM 標註的數據模型
├── controller/UserController.kt # 完整 CRUD 端點
└── ClientDemo.kt # 客戶端演示
// Gradle Kotlin DSL
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}dependencies {
implementation("com.github.metamessage:mm-spring-boot-starter:v0.2.9")
}dependencies {
implementation("com.github.metamessage:mm-client:v0.2.9")
}就這樣!
用 @field:MM 註解聲明字段元信息,ValueType 指定類型:
import io.github.metamessage.MM
import io.github.metamessage.ir.ValueType
data class User @JvmOverloads constructor(
@field:MM(desc = "用戶ID", type = ValueType.I64) var id: Long = 0L,
@field:MM(desc = "用戶名稱", type = ValueType.STR, min = "1", max = "50") var name: String = "",
@field:MM(desc = "電子郵箱", type = ValueType.EMAIL, allowEmpty = true) var email: String = "",
@field:MM(desc = "年齡", type = ValueType.U8, min = "0", max = "150", allowEmpty = true) var age: Int = 0,
@field:MM(desc = "是否激活", type = ValueType.BOOL) var active: Boolean = false
)使用 @MMGet/@MMPost 等組合註解自動設置 Content-Type,@MMBody/@MMQuery 聲明參數綁定方式,Schema 自動推斷無需 @MMSchema:
import io.github.metamessage.spring.boot.binder.MMBody
import io.github.metamessage.spring.boot.binder.MMQuery
import io.github.metamessage.spring.boot.web.MMGet
import io.github.metamessage.spring.boot.web.MMPost
import io.github.metamessage.spring.boot.web.MMPut
import io.github.metamessage.spring.boot.web.MMDelete
@RestController
@RequestMapping("/api/v1")
class UserController {
// GET:@MMQuery 從 ?data=<hex> 解碼為 SearchRequest
@MMGet(["/user/search"])
fun search(@MMQuery req: SearchRequest): APIResponse {
val user = users.find {
it.name.contains(req.name) && it.age in req.minAge..req.maxAge
} ?: throw NoSuchElementException("user not found")
return APIResponse(code = 200, message = "ok", data = user)
}
// POST:@MMBody 解碼請求體為 CreateUserRequest
@MMPost(["/user/create"])
fun create(@MMBody req: CreateUserRequest): APIResponse {
val user = User(id = nextId++, name = req.name, email = req.email, age = req.age)
users.add(user)
return APIResponse(code = 200, message = "user created", data = user)
}
// PUT:path variable + body
@MMPut(["/user/update/{id}"])
fun update(@PathVariable id: Long, @MMBody req: UpdateUserRequest): APIResponse {
// ...
}
// DELETE:無請求體
@MMDelete(["/user/delete/{id}"])
fun delete(@PathVariable id: Long): APIResponse {
// ...
}
}| 註解 | 作用 |
|---|---|
@MMGet(["/path"]) |
GET 端點,自動 produces = ["application/metamessage"] |
@MMPost(["/path"]) |
POST 端點,自動 consumes + produces |
@MMPut(["/path"]) |
PUT 端點,自動 consumes + produces |
@MMDelete(["/path"]) |
DELETE 端點,自動 produces |
@MMBody |
請求體參數,由 ArgumentResolver 解碼 |
@MMQuery |
查詢參數,從 ?data=<hex> 解碼 |
@MMSchema(type = X::class) |
可選,顯式指定 OPTIONS 樣例類型(覆蓋自動推斷) |
攔截器自動響應 OPTIONS 請求,返回請求類型的樣例 + Schema-Md5 校驗頭。Schema 類型按以下優先級推斷:
- 顯式
@MMSchema(type = X::class)—— 優先級最高 @MMQuery參數類型 —— 自動推斷@MMBody參數類型 —— 自動推斷- 無上述註解 —— 不返回樣例
OPTIONS /api/v1/user/create
→ 200 OK
Content-Type: application/metamessage
Schema-Md5: <md5-hex>
Access-Control-Max-Age: 86400
Allow: GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCH
Body: <MetaMessage 編碼的請求類型樣例>
客戶端可緩存 Schema-Md5 並在後續請求中攜帶,供服務端校驗。
mm-client 提供對標 mm-web-go 的泛型 HTTP 客戶端,每次請求前自動發送 OPTIONS 預檢並緩存 Schema-Md5。
import io.github.metamessage.client.MMClient
MMClient.setDefaultClient("http://localhost:8090", debug = true)使用 reified 泛型函數,類型安全,無需傳 Class 對象:
import io.github.metamessage.client.mmGet
import io.github.metamessage.client.mmPost
import io.github.metamessage.client.mmPut
import io.github.metamessage.client.mmDelete
// POST:body 以 wire 二進制作為請求體
val resp: APIResponse = mmPost<CreateUserRequest, APIResponse>(
"/api/v1/user/create",
CreateUserRequest(name = "David", email = "david@example.com", age = 28)
)
// GET:body 編碼為 hex 作為 ?data=<hex> 查詢參數,服務端 @MMQuery 自動解碼
val search: APIResponse = mmGet<SearchRequest, APIResponse>(
"/api/v1/user/search",
SearchRequest(name = "Alice", minAge = 20, maxAge = 40)
)
// GET:無請求體
val list: ListUsersResponse = mmGet<Unit, ListUsersResponse>("/api/v1/users")
// PUT / DELETE 同理
val updated: APIResponse = mmPut<UpdateUserRequest, APIResponse>(
"/api/v1/user/update/1",
UpdateUserRequest(name = "Alice Updated")
)
val deleted: APIResponse = mmDelete<Unit, APIResponse>("/api/v1/user/delete/3")val client = MMClient("http://localhost:8090", debug = true)
val resp: APIResponse = client.doRequest(
"POST",
"/api/v1/user/create",
CreateUserRequest(name = "David"),
APIResponse::class.java
)所有配置項前綴為 metamessage,均支持 matchIfMissing(不配置時默認啟用):
metamessage:
enabled: true # 總開關,false 則整個 starter 不生效examples 模塊提供一個完整的 Spring Boot 應用,演示:
| 端點 | 方法 | 說明 |
|---|---|---|
/api/v1/users |
GET | 列出所有用戶(wire) |
/api/v1/user/search |
GET | 按名稱+年齡範圍搜索用戶(wire) |
/api/v1/user/{id} |
GET | 獲取單個用戶(wire) |
/api/v1/user/create |
POST | 創建用戶(wire 請求體) |
/api/v1/user/update/{id} |
PUT | 更新用戶(wire) |
/api/v1/user/delete/{id} |
DELETE | 刪除用戶(wire) |
/api/v1/health |
GET | 健康檢查 |
# 啟動示例應用
./gradlew :examples:bootRun --args="--server.port=8090"啟動後 ClientDemo 會自動運行,依次演示 create → list → get → search → update → delete → health 全流程,並打印結果:
[ClientDemo] base URL: http://localhost:8090
[ClientDemo] create -> code=200 message=user created data=User(id=4, name=David, ...)
[ClientDemo] list -> total=4
- id=1 name=Alice email=alice@example.com age=30
- id=2 name=Bob email=bob@example.com age=25
...
[ClientDemo] get -> code=200 message=ok data=User(id=1, name=Alice, ...)
[ClientDemo] search -> code=200 message=ok data=User(id=1, name=Alice, ...)
[ClientDemo] update -> code=200 message=user updated data=User(id=1, name=Alice Updated, ...)
[ClientDemo] delete -> code=200 message=user deleted data=User(id=3, ...)
[ClientDemo] health -> status=ok
| 組件 | 版本 |
|---|---|
| Kotlin | 1.9.22 |
| Spring Boot | 3.2.5 |
| JDK | 17 |
| MetaMessage 核心庫 | com.github.metamessage:metamessage:v0.2.9(JitPack) |
| 構建工具 | Gradle 8.7(多模塊,Kotlin DSL) |
# 編譯全部模塊
./gradlew build
# 運行單元測試
./gradlew test
# 打包(跳過測試)
./gradlew build -x testMetaMessageCodecTest—— wire 編解碼、JSONC 編解碼 round-tripMetaMessageHttpMessageConverterTest—— wire 轉換器讀寫MetaMessageJsoncHttpMessageConverterTest—— JSONC 轉換器讀寫MetaMessageConverterTest—— ConverterFactory 字符串轉對象MMClientTest—— 用MockRestServiceServer驗證 OPTIONS 預檢、Schema-Md5 緩存、wire 請求/響應、GET 查詢參數
本項目跟隨 metamessage 生態的開源許可。