Java Springboot:如何使用WebClient而不是RestTemplate来执行非阻塞和异步调用
我有一个springboot项目,它使用springboot Resttemplate。我们已经从1.5.3迁移到springboot 2.0.1,我们正在努力 其余的调用通过使用WebClient进行异步调用。我们使用Resttemplate处理收到的字符串,如下所示。但WebClient只返回 Mono或Flux格式的数据。如何将数据获取为字符串。已尝试block()方法,但它执行异步调用Java Springboot:如何使用WebClient而不是RestTemplate来执行非阻塞和异步调用,java,spring,spring-boot,spring-webflux,Java,Spring,Spring Boot,Spring Webflux,我有一个springboot项目,它使用springboot Resttemplate。我们已经从1.5.3迁移到springboot 2.0.1,我们正在努力 其余的调用通过使用WebClient进行异步调用。我们使用Resttemplate处理收到的字符串,如下所示。但WebClient只返回 Mono或Flux格式的数据。如何将数据获取为字符串。已尝试block()方法,但它执行异步调用 @Retryable(maxAttempts=4,value=java.net.ConnectExce
@Retryable(maxAttempts=4,value=java.net.ConnectException.class,
退避=@backoff(延迟=3000,乘数=2))
公共Mono getResponse(字符串url){
返回webClient.get().uri(urlForCurrent.accept)(应用程序\u JSON)
.retrieve()
.bodyToMono(String.class);
}
使用RestTemplate显示数据流
@RequestMapping(value=traffic/,method=RequestMethod.GET,
products=MediaType.APPLICATION\u JSON\u值)
公共字符串getTraffic(@RequestParam(“place”)字符串位置)引发InterruptedException,ExecutionException{
String trafficJSON=Provider.getTrafficJSON(位置)
返回流量;
}
Provider.java
public String getTrafficJSON(String location) {
String url = ----;
ResponseEntity<String> response = dataFetcher.getResponse(url);
/// RESPONSEBODY IS READ AS STRING AND IT NEEDS TO BE PROCESSED
if (null != response {
return parser.transformJSON(response.getBody(), params);
}
return null;
}
公共字符串getTrafficJSON(字符串位置){
字符串url=---;
ResponseEntity response=dataFetcher.getResponse(url);
///RESPONSEBODY被读取为字符串,需要对其进行处理
if(null!=响应{
返回parser.transformJSON(response.getBody(),params);
}
返回null;
}
DataFetcher.java
@Retryable(最大尝试次数=4,
value=java.net.ConnectException.class,
退避=@backoff(延迟=3000,乘数=2))
公共响应属性getResponse(字符串url){
/* ----------------------- */
返回getRestTemplate().getForEntity(urlForCurrent,String.class);
}
第一步是使用baseUrl构建WebClient
对象
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")));
使用bodyToMono
的block()
函数等待响应。如果希望响应为字符串,可以使用google的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();
首先要了解的是,如果您需要调用
.block()
,您最好还是坚持使用restemplate
,使用WebClient将一无所获
如果你想从使用WebClient中获益,你需要从被动的角度来思考。被动过程实际上只是一系列步骤,每个步骤的输入都是之前步骤的输出。当请求进来时,你的代码会创建一系列步骤并立即返回,释放http线程。然后框架使用当上一步的输入可用时,执行每个步骤的工作线程池
这样做的好处是,在接受竞争请求的能力上获得了巨大的提升,而重新思考代码编写方式的成本很小。您的应用程序只需要一个非常小的http线程池和另一个非常小的工作线程池
当您的控制器方法返回一个Mono
或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();
});
}
@GetMapping(value=“endpoint”,products=MediaType.TEXT\u PLAIN\u value)
@应答器
@响应状态(OK)
公共单控制器方法(){
最终组件生成器生成器=
UriComponentsBuilder.fromHttpUrl(“http://base.url/“+”端点“)
.queryParam(“参数1”、“值”);
返回网络客户端
.get()
.uri(builder.build().encode().tori())
.accept(应用程序\u JSON\u UTF8)
.retrieve()
.bodyToMono(String.class)
.重试(4)
.doon错误(e->LOG.error(“Boom!”,e))
.map(s->{
//这是您的转换步骤。
//映射是同步的,因此将在处理响应的线程中运行。
//如果该步骤需要长时间运行,也可以使用flatMap(异步)。
//例如,如果需要调用数据库来进行转换。
返回s.toLowerCase();
});
}
转向反应式思考是一个相当大的范式转变,但值得付出努力。坚持下去,只要你能在整个应用程序中完全没有阻塞代码,这其实并不难。构建步骤并返回它们。然后让框架管理步骤的执行
如果有任何不清楚的地方,我们乐意提供更多的指导
记住要玩得开心:)因为有很多误解,所以这里我要澄清一些事情 Spring已经正式声明,他们将在将来不推荐使用
RestTemplate
,因此如果可以的话,如果您希望尽可能成为未来的证明,请使用WebClient
如合同中所述
注意:从5.0开始,非阻塞、反应式的org.springframework.web.reactive.client.WebClient
提供了一种现代的restemplate
替代方案,可有效支持同步和异步以及流式场景。RestTemplate
在将来的版本中将被弃用,并且今后不会添加主要的新功能。有关更多详细信息和示例代码,请参阅Spring框架参考文档的WebClient
部分
非反应性应用
@Retryable(maxAttempts = 4,
value = java.net.ConnectException.class,
backoff = @Backoff(delay = 3000, multiplier = 2))
public ResponseEntity<String> getResponse(String url) {
return webClient.get()
.uri(url)
.exchange()
.flatMap(response -> response.toEntity(String.class))
.block();
}