https://:8080/hello
構建響應式 RESTful Web 服務
本指南將引導您完成使用 Spring WebFlux 建立一個“Hello, Spring!” RESTful Web 服務的全過程,然後使用 WebClient 消費該服務。
| 本指南展示了使用 Spring WebFlux 的函式式方法。您也可以透過註解使用 WebFlux。 |
您將構建什麼
您將使用 Spring Webflux 構建一個 RESTful Web 服務,併為其構建一個 WebClient 消費者。您將能夠在 System.out 和
您需要準備什麼
-
大約 15 分鐘
-
一個喜歡的文字編輯器或 IDE
-
Java 17 或更高版本
-
您還可以將程式碼直接匯入到您的 IDE 中
如何完成本指南
與大多數 Spring 入門指南一樣,您可以從頭開始並完成每個步驟,也可以跳過您已熟悉的基本設定步驟。無論哪種方式,您最終都會得到可工作的程式碼。
要從頭開始,請轉到從 Spring Initializr 開始。
要跳過基礎知識,請執行以下操作
-
下載並解壓本指南的源儲存庫,或者使用Git克隆它:
git clone https://github.com/spring-guides/gs-reactive-rest-service.git -
進入
gs-reactive-rest-service/initial目錄 -
跳轉到建立 WebFlux 處理器。
完成後,您可以將您的結果與 gs-reactive-rest-service/complete 中的程式碼進行核對。
從 Spring Initializr 開始
您可以使用這個預初始化專案,然後點選“生成”下載一個 ZIP 檔案。此專案已配置為符合本教程中的示例。
手動初始化專案
-
導航到 https://start.spring.io。此服務會為您拉取應用程式所需的所有依賴項,併為您完成大部分設定。
-
選擇 Gradle 或 Maven,以及您想使用的語言:Kotlin 或 Java。
-
點選依賴項,然後選擇Spring Reactive Web。
-
單擊生成。
-
下載生成的 ZIP 檔案,這是一個已根據您的選擇配置好的 Web 應用程式存檔。
| 如果您的 IDE 集成了 Spring Initializr,您可以從 IDE 中完成此過程。 |
| 您還可以從 GitHub fork 專案,並在您的 IDE 或其他編輯器中開啟它。 |
建立 WebFlux 處理器
我們將從一個 Greeting 型別開始,它將由我們的 RESTful 服務序列化為 JSON。
package com.example.reactivewebservice;
public record Greeting(String message) {
}
package com.example.reactivewebservice
data class Greeting(val message: String)
在 Spring 響應式方法中,我們使用處理器來處理請求並建立響應,如下例所示
package com.example.reactivewebservice;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class GreetingHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(new Greeting("Hello, Spring!")));
}
}
package com.example.reactivewebservice
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono
@Component
class GreetingHandler {
fun hello(request: ServerRequest): Mono<ServerResponse> =
ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Greeting("Hello, Spring!"))
}
這個簡單的響應式類總是返回一個帶有“Hello, Spring!”問候語的 JSON 正文。它還可以返回許多其他東西,包括來自資料庫的項流、由計算生成的項流等等。請注意響應式程式碼:一個包含 ServerResponse 主體的 Mono 物件。
建立路由器
在此應用程式中,我們使用路由器來處理我們公開的唯一路由(/hello),如下例所示
package com.example.reactivewebservice;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
@Configuration(proxyBeanMethods = false)
public class GreetingRouter {
@Bean
public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {
return RouterFunctions.route(GET("/hello")
.and(accept(MediaType.APPLICATION_JSON)), greetingHandler::hello);
}
}
package com.example.reactivewebservice
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.router
@Configuration(proxyBeanMethods = false)
class GreetingRouter {
@Bean
fun route(greetingHandler: GreetingHandler): RouterFunction<ServerResponse> = router {
(accept(MediaType.APPLICATION_JSON) and GET("/hello")).invoke(greetingHandler::hello)
}
}
路由器監聽 /hello 路徑上的流量,並返回由我們的響應式處理程式類提供的值。
建立 WebClient
Spring 的 RestTemplate 類本質上是阻塞的。因此,我們不希望在響應式應用程式中使用它。對於響應式應用程式,Spring 提供了 WebClient 類,它是非阻塞的。我們使用基於 WebClient 的實現來消費我們的 RESTful 服務
package com.example.reactivewebservice;
import reactor.core.publisher.Mono;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
@Component
public class GreetingClient {
private final WebClient client;
// Spring Boot auto-configures a WebClient.Builder instance with nice defaults and customizations.
// We can use it to create a dedicated WebClient for our component.
public GreetingClient(WebClient.Builder builder) {
this.client = builder.baseUrl("https://:8080").build();
}
public Mono<String> getMessage() {
return this.client.get().uri("/hello").accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Greeting.class)
.map(Greeting::message);
}
}
package com.example.reactivewebservice
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.bodyToMono
import reactor.core.publisher.Mono
@Component
class GreetingClient(builder: WebClient.Builder) {
// Spring Boot auto-configures a WebClient.Builder instance with nice defaults and customizations.
// We can use it to create a dedicated WebClient for our component.
private val client: WebClient = builder.baseUrl("https://:8080").build()
fun getMessage(): Mono<String> = client
.get()
.uri("/hello")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono<Greeting>()
.map { it.message }
}
WebClient 類使用響應式特性,以 Mono 的形式儲存訊息內容(由 getMessage 方法返回)。這使用的是函式式 API,而不是命令式 API,來鏈式呼叫響應式運算子。
可能需要一些時間來適應響應式 API,但 WebClient 具有有趣的特性,也可以在傳統的 Spring MVC 應用程式中使用。
您也可以使用 WebClient 與非響應式、阻塞服務進行通訊。 |
使應用程式可執行
我們將使用 main() 方法來驅動我們的應用程式並從我們的端點獲取 Greeting 訊息。
package com.example.reactivewebservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class ReactiveWebServiceApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ReactiveWebServiceApplication.class, args);
GreetingClient greetingClient = context.getBean(GreetingClient.class);
// We need to block for the content here or the JVM might exit before the message is logged
System.out.println(">> message = " + greetingClient.getMessage().block());
}
}
package com.example.reactivewebservice
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class ReactiveWebServiceApplication
fun main(args: Array<String>) {
val context = runApplication<ReactiveWebServiceApplication>(*args)
val greetingClient = context.getBean(GreetingClient::class.java)
// Block here to wait for the response, otherwise the JVM might exit before the message is logged
println(">> message = ${greetingClient.getMessage().block()}")
}
@SpringBootApplication 是一個方便的註解,它添加了以下所有內容
-
@Configuration:將類標記為應用程式上下文的 bean 定義源。 -
@EnableAutoConfiguration:告訴 Spring Boot 根據類路徑設定、其他 bean 和各種屬性設定開始新增 bean。例如,如果spring-webmvc在類路徑中,此註解會將應用程式標記為 Web 應用程式並激活關鍵行為,例如設定DispatcherServlet。 -
@ComponentScan:告訴 Spring 在hello包中查詢其他元件、配置和服務,讓它找到控制器。
main() 方法使用 Spring Boot 的 SpringApplication.run() 方法啟動應用程式。您是否注意到沒有一行 XML?也沒有 web.xml 檔案。這個 Web 應用程式是 100% 純 Java,您不必處理任何管道或基礎設施的配置。
構建可執行 JAR
您可以使用 Gradle 或 Maven 從命令列執行應用程式。您還可以構建一個包含所有必要依賴項、類和資源並執行的單個可執行 JAR 檔案。構建可執行 JAR 使在整個開發生命週期中,跨不同環境等,輕鬆交付、版本化和部署服務作為應用程式。
如果您使用 Gradle,您可以透過使用 ./gradlew bootRun 執行應用程式。或者,您可以透過使用 ./gradlew build 構建 JAR 檔案,然後按如下方式執行 JAR 檔案
如果您使用 Maven,您可以透過使用 ./mvnw spring-boot:run 執行應用程式。或者,您可以使用 ./mvnw clean package 構建 JAR 檔案,然後按如下方式執行 JAR 檔案
| 這裡描述的步驟建立了一個可執行的 JAR。您還可以構建一個經典的 WAR 檔案。 |
日誌輸出已顯示。服務應在幾秒鐘內啟動並執行。
服務啟動後,您會看到一行
>> message = Hello, Spring!
該行來自 WebClient 消費的響應式內容。當然,您可以找到比將其放入 System.out 中更有趣的方法來處理您的輸出。
測試應用程式
現在應用程式正在執行,您可以對其進行測試。首先,您可以開啟瀏覽器並訪問 https://:8080/hello,然後會看到“Hello, Spring!”。對於本指南,我們還建立了一個測試類,以幫助您開始使用 WebTestClient 類進行測試。
package com.example.reactivewebservice;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
// We create a @SpringBootTest, starting an actual server on a RANDOM_PORT
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GreetingRouterTest {
// Spring Boot will create a WebTestClient for you,
// already configure and ready to issue requests against "localhost:RANDOM_PORT"
@Autowired
private WebTestClient webTestClient;
@Test
public void testHello() {
webTestClient
// Create a GET request to test an endpoint
.get().uri("/hello")
.accept(MediaType.APPLICATION_JSON)
.exchange()
// and use the dedicated DSL to test assertions against the response
.expectStatus().isOk()
.expectBody(Greeting.class).value(greeting -> {
assertThat(greeting.message()).isEqualTo("Hello, Spring!");
});
}
}
package com.example.reactivewebservice
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
// We create a @SpringBootTest, starting an actual server on a RANDOM_PORT
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GreetingRouterTest(@Autowired private val webTestClient: WebTestClient) {
// Spring Boot will create a WebTestClient for you,
// already configured and ready to issue requests against "localhost:RANDOM_PORT"
@Test
fun testHello() {
webTestClient
// Create a GET request to test an endpoint
.get().uri("/hello")
.accept(MediaType.APPLICATION_JSON)
.exchange()
// and use the dedicated DSL to test assertions against the response
.expectStatus().isOk
.expectBody<Greeting>()
.isEqualTo(Greeting("Hello, Spring!"))
}
}
總結
恭喜!您已經開發了一個包含 WebClient 的響應式 Spring 應用程式來消費 RESTful 服務!
想寫新指南或為現有指南做貢獻嗎?請檢視我們的貢獻指南。
| 所有指南的程式碼均採用 ASLv2 許可,文字內容採用署名-禁止演繹知識共享許可。 |