領先一步 (Get ahead)
VMware 提供培訓和認證,以加速您的進展。
了解更多 (Learn more)更新:另請參閱 Spring Fu 實驗性專案。
自從我們最初發布 Spring Framework 5 中官方 Kotlin 支援(受到社群廣泛好評!)以來,我們持續努力以在 Spring WebFlux 的最新改進之上,提供更強大的 Kotlin 支援。
為了展示這些功能以及它們如何一起使用,我建立了一個新的 spring-kotlin-functional 示範應用程式,它是一個獨立的 Spring WebFlux 應用程式,使用 Kotlin 開發,具有 Mustache 樣板呈現、JSON REST webservices 和伺服器發送事件 (Server-Sent Events) 串流功能。請務必在預計 9 月發布的 Spring Framework 5 之前,向我們發送回饋和建議。
Spring WebFlux 和 Reactor Netty 允許程式化啟動應用程式,因為它們本來就被設計為作為嵌入式 Web 伺服器執行。當開發 Spring Boot 應用程式時,這顯然不是必需的,但對於在微服務架構或其他受限環境中,具有自定義啟動的緊密部署單元非常有用。
class Application {
private val httpHandler: HttpHandler
private val server: HttpServer
private var nettyContext: BlockingNettyContext? = null
constructor(port: Int = 8080) {
val context = GenericApplicationContext().apply {
beans().initialize(this)
refresh()
}
server = HttpServer.create(port)
httpHandler = WebHttpHandlerBuilder.applicationContext(context).build()
}
fun start() {
nettyContext = server.start(ReactorHttpHandlerAdapter(httpHandler))
}
fun startAndAwait() {
server.startAndAwait(ReactorHttpHandlerAdapter(httpHandler),
{ nettyContext = it })
}
fun stop() {
nettyContext?.shutdown()
}
}
fun main(args: Array<String>) {
Application().startAndAwait()
}
Spring Framework 5 引入了一種使用 Lambda 表達式註冊 Bean 的新方式。它非常有效率,不需要任何反射或 CGLIB 代理(因此 kotlin-spring
外掛程式對於 Reactive 應用程式來說不是必需的),並且非常適合 Java 8 或 Kotlin 等語言。您可以在此處查看 Java 與 Kotlin 語法的概觀。
在 spring-kotlin-functional 中,Bean 在包含 Bean 定義的 Beans.kt
檔案中聲明。 DSL 在概念上透過一個簡潔的聲明式 API 聲明一個 Consumer<GenericApplicationContext>
,讓您可以處理配置檔案和 Environment
,以自訂 Bean 的註冊方式。此 DSL 還允許透過 if
表達式、for
迴圈或任何其他 Kotlin 結構自訂 Bean 的註冊邏輯。
beans {
bean<UserHandler>()
bean<Routes>()
bean<WebHandler>("webHandler") {
RouterFunctions.toWebHandler(
ref<Routes>().router(),
HandlerStrategies.builder().viewResolver(ref()).build()
)
}
bean("messageSource") {
ReloadableResourceBundleMessageSource().apply {
setBasename("messages")
setDefaultEncoding("UTF-8")
}
}
bean {
val prefix = "classpath:/templates/"
val suffix = ".mustache"
val loader = MustacheResourceTemplateLoader(prefix, suffix)
MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply {
setPrefix(prefix)
setSuffix(suffix)
}
}
profile("foo") {
bean<Foo>()
}
}
在此範例中,bean<Routes>()
使用建構函式自動裝配,而 ref<Routes>()
是 applicationContext.getBean(Routes::class.java)
的捷徑。
Kotlin 的一個關鍵功能是 Null 安全性,它允許在編譯時處理 null
值,而不是在運行時遇到著名的 NullPointerException
。 這透過乾淨的空值宣告,表達「值或無值」語意,而無需支付像 Optional
這樣的包裝器的成本,從而使您的應用程式更安全。(Kotlin 允許將函數結構與可為空的值一起使用;查看這個 Kotlin Null 安全性綜合指南。)
雖然 Java 不允許在其型別系統中表達 Null 安全性,但我們透過工具友好的註釋,為 Spring API 引入了一些 Null 安全性:套件層級的 @NonNullApi
註釋宣告非空是預設行為,我們明確地將 @Nullable
註釋放在特定的參數或傳回值可以為 null
的地方。 我們為整個 Spring Framework API 完成了這項工作(是的,這是一項巨大的努力!),其他專案如 Spring Data 開始利用它。 Spring 註釋使用 JSR 305 元註釋(一個休眠的 JSR,但受到 IDEA、Eclipse、Findbugs 等工具的支援)進行元註釋,以向 Java 開發人員提供有用的警告。
在 Kotlin 端,一個殺手級功能是 - 從 Kotlin 1.1.51 版本開始 - 這些註釋 被 Kotlin 識別,以便為整個 Spring API 提供 Null 安全性。 這意味著在使用 Spring 5 和 Kotlin 時,您的程式碼中永遠不應該有 NullPointerException
,因為編譯器不允許這樣做。 您需要使用 -Xjsr305=strict
編譯器標誌才能在 Kotlin 型別系統中考慮這些註釋。
spring-kotlin-functional
沒有使用 @RestController
和 @RequestMapping
,而是透過專用的 Kotlin DSL 使用 WebFlux 函數式 API。
router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
}
與 Bean DSL 一樣,函數式路由 DSL 允許根據自訂邏輯和動態資料程式化註冊路由(可用於開發 CMS 或電子商務解決方案,其中大多數路由取決於透過後台建立的資料)。
路由通常指向負責基於 HTTP 請求建立 HTTP 回應的處理程序,透過可呼叫參考。 這是 UserHandler
,它利用 Spring Framework 5 直接在 Spring JAR 中提供的 Kotlin 擴展,以避免使用 Kotlin 具體化型別參數的眾所周知的型別擦除問題。 在 Java 中,相同的程式碼需要額外的 Class
或 ParameterizedTypeReference
參數。
class UserHandler {
private val users = Flux.just(
User("Foo", "Foo", LocalDate.now().minusDays(1)),
User("Bar", "Bar", LocalDate.now().minusDays(10)),
User("Baz", "Baz", LocalDate.now().minusDays(100)))
private val userStream = Flux
.zip(Flux.interval(ofMillis(100)), users.repeat())
.map { it.t2 }
fun findAll(req: ServerRequest) =
ok().body(users)
fun findAllView(req: ServerRequest) =
ok().render("users", mapOf("users" to users.map { it.toDto() }))
fun stream(req: ServerRequest) =
ok().bodyToServerSentEvents(userStream)
}
請注意,使用 Spring WebFlux 建立伺服器發送事件端點非常容易,以及伺服器端樣板呈現(在本應用程式中為 Mustache)。
##使用 WebClient、Reactor Test 和 JUnit 5 輕鬆測試
Kotlin 允許在反引號之間指定有意義的測試函數名稱,並且從 JUnit 5.0 RC2 開始,Kotlin 測試類別可以使用 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
來啟用測試類別的單一實例化,這允許在非靜態方法上使用 @BeforeAll
和 @AfterAll
註釋,這非常適合 Kotlin。 現在也可以透過帶有 junit.jupiter.testinstance.lifecycle.default = per_class
屬性的 junit-platform.properties
檔案,將預設行為變更為 PER_CLASS
。
class IntegrationTests {
val application = Application(8181)
val client = WebClient.create("https://127.0.0.1:8181")
@BeforeAll
fun beforeAll() {
application.start()
}
@Test
fun `Find all users on JSON REST endpoint`() {
client.get().uri("/api/users")
.accept(APPLICATION_JSON)
.retrieve()
.bodyToFlux<User>()
.test()
.expectNextMatches { it.firstName == "Foo" }
.expectNextMatches { it.firstName == "Bar" }
.expectNextMatches { it.firstName == "Baz" }
.verifyComplete()
}
@Test
fun `Find all users on HTML page`() {
client.get().uri("/users")
.accept(TEXT_HTML)
.retrieve()
.bodyToMono<String>()
.test()
.expectNextMatches { it.contains("Foo") }
.verifyComplete()
}
@Test
fun `Receive a stream of users via Server-Sent-Events`() {
client.get().uri("/api/users")
.accept(TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux<User>()
.test()
.expectNextMatches { it.firstName == "Foo" }
.expectNextMatches { it.firstName == "Bar" }
.expectNextMatches { it.firstName == "Baz" }
.expectNextMatches { it.firstName == "Foo" }
.expectNextMatches { it.firstName == "Bar" }
.expectNextMatches { it.firstName == "Baz" }
.thenCancel()
.verify()
}
@AfterAll
fun afterAll() {
application.stop()
}
}
##結論
我們期待收到有關這些新功能的反饋! 請注意,8 月是我們改進 API 的最後機會,因為預計在本月底發布 最終的 Spring Framework 5.0 發布候選版本。 因此,請隨意使用 spring-kotlin-functional,Fork 它,新增 Spring Data Reactive Fluent API 等新功能。
在我們這一邊,我們現在正在編寫文件。
祝您編碼愉快 ;-)