我有一个使用Springboot Resttemplate的springboot项目。我们已经从1.5.3迁移到springboot 2.0.1,我们正在努力使 其余的调用通过使用WebClient异步进行。我们曾经使用Resttemplate处理接收到的字符串,如下所示。但是WebClient仅返回 单声道或助焊剂中的数据。我怎样才能以String形式获取数据。已经尝试过block()方法,但是它执行异步调用。
@Retryable(maxAttempts = 4, value = java.net.ConnectException.class,
backoff = @Backoff(delay = 3000, multiplier = 2))
public Mono<String> getResponse(String url) {
return webClient.get().uri(urlForCurrent).accept(APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class);
}
使用RestTemplate呈现数据流
Controller.java
@RequestMapping(value = traffic/, method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public String getTraffic(@RequestParam("place") String location) throws InterruptedException, ExecutionException {
String trafficJSON = Provider.getTrafficJSON(location)
return trafficJSON;
}
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;
}
DataFetcher.java
@Retryable(maxAttempts = 4,
value = java.net.ConnectException.class,
backoff = @Backoff(delay = 3000, multiplier = 2))
public ResponseEntity<String> getResponse(String url) {
/* ----------------------- */
return getRestTemplate().getForEntity(urlForCurrent, String.class);
}
答案 0 :(得分:4)
由于存在很多误解,所以在这里我要澄清一些事情。
Spring正式声明,它们将在未来弃用RestTemplate
,因此,如果可以的话,请使用WebClient
。
注意::自5.0以来,无阻塞,反应式
org.springframework.web.reactive.client.WebClient
提供了RestTemplate
的现代替代方案,并有效支持同步和异步以及流方案。RestTemplate
将在以后的版本中弃用,并且以后不会添加任何主要的新功能。有关更多详细信息和示例代码,请参见Spring Framework参考文档的WebClient
部分。
非反应性应用
如果您的应用程序是非反应性应用程序(不向调用方客户返回磁通量或单声道),则需要使用block()
(如果需要该值)。您当然可以在应用程序内部使用Mono
或Flux
,但是最后您必须调用block()
以获得返回给调用方客户端所需的具体值。
非反应式应用程序使用tomcat
作为基础服务器实现,这是一个基于servlet的服务器,它将为每个请求分配1个线程,因此您将无法获得反应式应用程序带来的性能提升。
最新版本的Tomcat支持最新的servlet规范+3.0,该规范反过来又支持非阻塞I / O ,但是对于WebFlux应用程序的这种支持方式在Spring之前尚不清楚。文档。
反应性应用
另一方面,如果您有反应性应用程序,则在任何情况下都不应在应用程序中调用block()
或subscribe()
。阻塞正是它所说的,它将阻塞一个线程并阻塞该线程的执行,直到可以继续执行为止,这在响应式世界中是很糟糕的。
这里的基础服务器实现是netty
服务器,它是非servlet,基于事件的服务器,将不为每个请求分配一个线程,该服务器本身是线程不可知的,并且任何线程可用将在任何请求期间随时处理任何事情。
我怎么知道我拥有什么应用程序?
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)
来强制执行选择。
The “Spring WebFlux Framework”
那么如何实现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();
}
这是最简单,侵入性最小的实现。当然,您可能需要在@Bean
中构建适当的Web客户端,并将其自动连接到其类中。
答案 1 :(得分:1)
第一步是使用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")));
用block()
的{{1}}功能等待响应。如果您希望将响应作为字符串,则可以使用Google的gson库将其转换。
bodyToMono
如果您不想知道api调用的状态,则可以执行以下操作。
Object response = responseSpec.bodyToMono(Object.class).block();
Gson gson = new Gson();
String str = gson.toJson(response);
答案 2 :(得分:1)
首先要了解的是,如果您需要致电.block()
,那么您最好还是坚持使用RestTemplate
,使用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();
});
}
转向反应式思维是一个很大的范式转变,但值得付出努力。坚持下去,一旦您可以全神贯注地在整个应用程序中完全没有阻塞代码,这实际上并不那么困难。建立步骤并返回它们。然后,让框架管理步骤的执行。
如果其中任何一个不清楚,很乐意提供更多指导。
记住玩得开心:)
答案 3 :(得分:0)
据我所知,您的 Spring Boot Web 应用程序中需要非阻塞/异步和 Web servlet/同步 api 调用。我有完全相同的问题。答案很简单。您需要同时拥有 spring-boot-starter-web 和 spring-boot-starter-webflux 依赖项。 然后,当您要进行异步/非阻塞调用时,请确保按如下方式调用 toFuture() 方法。
configWebClient.post().uri("/alarm/check")
.body(BodyInserters.fromFormData(formData))
.exchangeToMono(res -> {
if (res.statusCode().equals(HttpStatus.OK)) {
return res.bodyToMono(Boolean.class);
} else {
return res.createException().flatMap(Mono::error);
}
}).toFuture();