3 回答

TA貢獻1804條經驗 獲得超8個贊
由于存在很多誤解,所以在這里我要澄清一些事情。
Spring 已經正式聲明,如果可以的話,如果你想盡可能地證明未來,請使用RestTemplate
它。maintenence mode
WebClient
如RestTemplate API中所述
注意:從 5.0 開始,這個類處于維護模式,只有少量的更改請求和錯誤被接受。請考慮使用
org.springframework.web.reactive.client.WebClient
具有更現代 API 并支持同步、異步和流式傳輸方案的 。
非反應性應用
如果您的應用程序是非反應性應用程序(不向調用客戶端返回通量或單聲道),您必須做的是block()
在需要該值時使用。您當然可以在您的應用程序內部使用Mono
or?Flux
,但最后您必須調用block()
以獲取返回給調用客戶端所需的具體值。
非反應性應用程序使用例如作為底層服務器實現,它遵循 servlet 規范,因此它將為每個請求分配 1 個線程,因此您將無法獲得反應性應用程序所獲得的性能提升tomcat
。undertow
響應式應用
另一方面,如果您有一個反應性應用程序,則在任何情況下都不應調用block()
您的應用程序。阻塞正是它所說的,它會阻塞一個線程并阻止該線程執行直到它可以繼續,這在反應世界中是不好的。
您也不應該調用subscribe
您的應用程序,除非您的應用程序是響應的最終消費者。例如,如果您正在調用一個 api 來獲取數據并寫入您的應用程序連接到的數據庫。您的后端應用程序是最終消費者。如果外部客戶端正在調用您的后端(例如反應、角度應用程序、移動客戶端等),則外部客戶端是最終消費者,并且是訂閱者。不是你。
這里的底層默認服務器實現是一個netty
服務器,它是一個非 servlet、基于事件的服務器,不會為每個請求分配一個線程,服務器本身是線程不可知的,任何可用的線程都將在任何請求期間隨時處理任何事情。
webflux文檔清楚地指出,servlet 3.1+ 支持的服務器 tomcat 和 jetty 都可以與 webflux 以及非 serlet 服務器 netty 和 undertow 一起使用。
我怎么知道我有什么應用程序?
Spring 指出,如果您同時擁有spring-web
和spring-webflux
在類路徑中,則應用程序將支持spring-web
并默認啟動具有底層 tomcat 服務器的非反應性應用程序。
如果需要作為彈簧狀態,可以手動覆蓋此行為。
在您的應用程序中同時添加
spring-boot-starter-web
和spring-boot-starter-webflux
模塊會導致 Spring Boot 自動配置 Spring MVC,而不是 WebFlux。選擇此行為是因為許多 Spring 開發人員將其添加spring-boot-starter-webflux
到他們的 Spring MVC 應用程序中以使用反應式 WebClient。您仍然可以通過將所選應用程序類型設置為 來強制執行您的選擇SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)
。
“Spring WebFlux 框架”
那么如何按照題目提供的代碼實現WebClient呢?
@Retryable(maxAttempts = 4,
? ? ? ?value = java.net.ConnectException.class,
? ? ? ?backoff = @Backoff(delay = 3000, multiplier = 2))
public Mono<String> getResponse(String url) {
? ? return webClient.get()
? ? ? ? ? ? .uri(url)
? ? ? ? ? ? .exchange()
? ? ? ? ? ? .flatMap(response -> response.toEntity(String.class));
}
我會說這是最簡單和最少侵入性的實現。您當然需要構建一個合適的網絡客戶端,@Bean并將其自動連接到它的類中。

TA貢獻1868條經驗 獲得超4個贊
第一步是WebClient
使用 baseUrl 構建對象;
WebClient webClient = WebClient.builder() .baseUrl("http://localhost:8080/api") //baseUrl .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build();
然后選擇方法并將路徑與請求變量或正文有效負載一起附加。
ResponseSpec responseSpec = webClient .get() .uri(uriBuilder -> uriBuilder.path("/findById") //additional path .queryParam("id", id).build()) .retrieve() .onStatus(HttpStatus::is4xxClientError, response -> Mono.error(new CustomRuntimeException("Error")));
等待響應的block()
功能bodyToMono
。如果你想要響應作為字符串,你可以使用谷歌的 gson 庫來轉換它。
Object response = responseSpec.bodyToMono(Object.class).block();Gson gson = new Gson();String str = gson.toJson(response);
如果你不想知道 api 調用的狀態,你可以像下面那樣做。
webClient .post() .uri(uri -> uri.path("/save").build()) .body( BodyInserters.fromObject(payload) ) .exchange().subscribe();

TA貢獻1803條經驗 獲得超3個贊
首先要了解的是,如果您需要調用,.block()不妨堅持使用RestTemplate,使用 WebClient 將一無所獲。
如果你想從使用 WebClient 中獲益,你需要開始以反應的方式思考。反應過程實際上只是一系列步驟,每個步驟的輸入都是前一步的輸出。當收到請求時,您的代碼會創建步驟序列并立即返回并釋放 http 線程。當上一步的輸入可用時,框架然后使用工作線程池來執行每個步驟。
這樣做的好處是在接受競爭請求的能力上獲得了巨大的收益,而代價很小,而不得不重新考慮您編寫代碼的方式。您的應用程序只需要一個非常小的 http 線程池和另一個非常小的工作線程池。
當您的控制器方法返回Monoor時Flux,您就做對了,不需要調用block().
像這樣的最簡單的形式:
@GetMapping(value = "endpoint", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
@ResponseStatus(OK)
public Mono<String> controllerMethod() {
final UriComponentsBuilder builder =
UriComponentsBuilder.fromHttpUrl("http://base.url/" + "endpoint")
.queryParam("param1", "value");
return webClient
.get()
.uri(builder.build().encode().toUri())
.accept(APPLICATION_JSON_UTF8)
.retrieve()
.bodyToMono(String.class)
.retry(4)
.doOnError(e -> LOG.error("Boom!", e))
.map(s -> {
// This is your transformation step.
// Map is synchronous so will run in the thread that processed the response.
// Alternatively use flatMap (asynchronous) if the step will be long running.
// For example, if it needs to make a call out to the database to do the transformation.
return s.toLowerCase();
});
}
轉向反應式思考是一個相當大的范式轉變,但值得付出努力。堅持住,一旦您能夠在整個應用程序中完全沒有阻塞代碼,這真的不是那么困難。構建步驟并返回它們。然后讓框架管理步驟的執行。
如果有任何不清楚的地方,我們很樂意提供更多指導。
記得玩得開心:)
添加回答
舉報