Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Springboot : How to use WebClient instead of RestTemplate for Performing Non blocking and Asynchronous calls

I have a springboot project which uses Springboot Resttemplate. We have moved to springboot 2.0.1 from 1.5.3 and we are trying to make the rest calls from it asynchronous by using WebClient. We used to process the string received using Resttemplate as given below. But WebClient returns only data in Mono or Flux. How can I get the data as String. Already tried block() method , but it does asynchronous calls.

@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);
}

Present Data flow with RestTemplate

  1. Controller receives the client call
  2. provider gets the data in String format
  3. Provider processes the String
  4. Data is given to controller

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);
}
like image 379
Abhi Avatar asked Aug 05 '19 09:08

Abhi


People also ask

Can I use WebClient instead of RestTemplate?

Spring 5 documentation suggests that WebClient is now the preferred way to make HTTP requests. WebClient is part of Spring WebFlux and is intended to replace the classic RestTemplate. Compared to RestTemplate , WebClient has a more functional feel and is fully reactive. Since Spring 5.0, RestTemplate is deprecated.

What is the alternative of RestTemplate in spring boot?

RestTemplate will still be used. But in some cases, the non-blocking approach uses much fewer system resources compared to the blocking one. So, WebClient is a preferable choice in those cases.

Which is better RestTemplate or WebClient?

One of the main differences is RestTemplate is synchronous and blocking i.e. when you do a rest call you need to wait till the response comes back to proceed further. But WebClient is complete opposite of this. The caller need not wait till response comes back. Instead he will be notified when there is a response.

Is Spring WebClient async?

The Spring WebClient is a reactive HTTP library; it's the follow-up to the Spring RestTemplate which is now in maintenance mode. Also, whereas the RestTemplate was a synchronous blocking library, WebClient is an asynchronous non-blocking library.


2 Answers

The first thing to understand is if you are needing to call .block() you might as well stick with RestTemplate, using WebClient will gain you nothing.

You need to start thinking in reactive terms if you want to gain from using WebClient. A reactive process is really just a sequence of steps, the input of each step being the output of the step before it. When a request comes in, your code creates the sequence of steps and returns immediately releasing the http thread. The framework then uses a pool of worker threads to execute each step when the input from the previous step becomes available.

The benefit is a huge gain in capacity to accept competing requests at the small cost of having to rethink the way you write code. Your application will only need a very small pool of http threads and another very small pool of worker threads.

When your controller method is returning a Mono or Flux, you have got it right and there will be no need to call block().

Something like this in it's simplest form:

@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();
            });
}

Moving to thinking in reactive is a pretty big paradigm shift, but well worth the effort. Hang in there, it's really not that difficult once you can wrap your head around having no blocking code at all in your entire application. Build the steps and return them. Then let the framework manage the executions of the steps.

Happy to provide more guidance if any of this is not clear.

Remember to have fun :)

like image 61
Captain Avatar answered Sep 21 '22 05:09

Captain


The first step is to build WebClient object with the baseUrl;

WebClient webClient = WebClient.builder()
    .baseUrl("http://localhost:8080/api") //baseUrl
    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .build();

Then choose the method and append the path along with the request variables or body payload.

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")));

Wait for the response with block() function of bodyToMono. If you want response as String, you can convert it using google's gson library.

Object response = responseSpec.bodyToMono(Object.class).block();
Gson gson = new Gson();
String str = gson.toJson(response);

If you don't want to need to know the status of the api call, you can do like following.

webClient
    .post()
    .uri(uri -> uri.path("/save").build())
    .body( BodyInserters.fromObject(payload) )
    .exchange().subscribe();
like image 42
Alexpandiyan Chokkan Avatar answered Sep 17 '22 05:09

Alexpandiyan Chokkan