CompletionStage
and its implementation CompletableFuture
, and Java began to use these functions in specifications such as the Reactive Client API in JAX-RS. Response response = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .get();
async()
method, as shown in Listing 2. Future<Response> response = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .async() .get();
Future
instance with type javax.ws.rs.core.Response
. This can lead to polling the response, calling future.get()
, or registering a callback, which will be called when there is an available HTTP response. Both implementations are suitable for asynchronous programming, but are usually complicated if you want to group callbacks or add conditional cases to these asynchronous execution minima.rx()
method during client building. In Listing 3 rx()
method returns a reactive invoker that exists during client execution, and the client returns a response with the type CompletionStage.rx()
, which allows the transition from synchronous invoker to asynchronous using a simple call. CompletionStage<Response> response = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .rx() .get();
CompletionStage<>
is a new interface introduced in Java 8. It represents a calculation, which can be a step in a larger calculation, as the name suggests. This is the only representative of Java 8 reactivity that got into JAX-RS.AcceptAsync()
, where I can provide a snippet of code that will be executed asynchronously when the answer becomes available, as shown in Listing 4. response.thenAcceptAsync(res -> { Temperature t = res.readEntity(Temperature.class); //do stuff with t });
Forecast
class is defined, which wraps the Location
and Temperature
classes. public class Temperature { private Double temperature; private String scale; // getters & setters } public class Location { String name; public Location() {} public Location(String name) { this.name = name; } // getters & setters } public class Forecast { private Location location; private Temperature temperature; public Forecast(Location location) { this.location = location; } public Forecast setTemperature( final Temperature temperature) { this.temperature = temperature; return this; } // getters }
ServiceResponse
class ServiceResponse
implemented in Listing 6. public class ServiceResponse { private long processingTime; private List<Forecast> forecasts = new ArrayList<>(); public void setProcessingTime(long processingTime) { this.processingTime = processingTime; } public ServiceResponse forecasts(List<Forecast> forecasts) { this.forecasts = forecasts; return this; } // getters }
LocationResource
shown in Listing 7 defines three sample locations returned with the /location
path. @Path("/location") public class LocationResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocations() { List<Location> locations = new ArrayList<>(); locations.add(new Location("London")); locations.add(new Location("Istanbul")); locations.add(new Location("Prague")); return Response.ok(new GenericEntity<List<Location>>(locations){}).build(); } }
TemperatureResource
, shown in Listing 8, returns a randomly generated temperature value between 30 and 50 for a given location. A 500 ms delay is added to the implementation to simulate the sensor readout. @Path("/temperature") public class TemperatureResource { @GET @Path("/{city}") @Produces(MediaType.APPLICATION_JSON) public Response getAverageTemperature(@PathParam("city") String cityName) { Temperature temperature = new Temperature(); temperature.setTemperature((double) (new Random().nextInt(20) + 30)); temperature.setScale("Celsius"); try { Thread.sleep(500); } catch (InterruptedException ignored) { ignored.printStackTrace(); } return Response.ok(temperature).build(); } }
ForecastResource
implementation (see Listing 9), which gives all the locations. Then, for each position, it calls temperature service to get the values in degrees Celsius. @Path("/forecast") public class ForecastResource { @Uri("location") private WebTarget locationTarget; @Uri("temperature/{city}") private WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocationsWithTemperature() { long startTime = System.currentTimeMillis(); ServiceResponse response = new ServiceResponse(); List<Location> locations = locationTarget .request() .get(new GenericType<List<Location>>(){}); locations.forEach(location -> { Temperature temperature = temperatureTarget .resolveTemplate("city", location.getName()) .request() .get(Temperature.class); response.getForecasts().add( new Forecast(location).setTemperature(temperature)); }); long endTime = System.currentTimeMillis(); response.setProcessingTime(endTime - startTime); return Response.ok(response).build(); } }
/forecast
, you will get a conclusion similar to that shown in Listing 10. Note that the request processing took 1.533 ms, which is logical, since a synchronous request for temperature values from three different locations adds up to 1.5 ms { "forecasts": [ { "location": { "name": "London" }, "temperature": { "scale": "Celsius", "temperature": 33 } }, { "location": { "name": "Istanbul" }, "temperature": { "scale": "Celsius", "temperature": 38 } }, { "location": { "name": "Prague" }, "temperature": { "scale": "Celsius", "temperature": 46 } } ], "processingTime": 1533 }
@Path("/reactiveForecast") public class ForecastReactiveResource { @Uri("location") private WebTarget locationTarget; @Uri("temperature/{city}") private WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) public void getLocationsWithTemperature(@Suspended final AsyncResponse async) { long startTime = System.currentTimeMillis(); // (stage) CompletionStage<List<Location>> locationCS = locationTarget.request() .rx() .get(new GenericType<List<Location>>() {}); // , // , , // CompletionStage final CompletionStage<List<Forecast>> forecastCS = locationCS.thenCompose(locations -> { // // ompletionStage List<CompletionStage<Forecast>> forecastList = // // locations.stream().map(location -> { // // // final CompletionStage<Temperature> tempCS = temperatureTarget .resolveTemplate("city", location.getName()) .request() .rx() .get(Temperature.class); // CompletableFuture, // // return CompletableFuture.completedFuture( new Forecast(location)) .thenCombine(tempCS, Forecast::setTemperature); }).collect(Collectors.toList()); // CompletableFuture, // completable future // return CompletableFuture.allOf( forecastList.toArray( new CompletableFuture[forecastList.size()])) .thenApply(v -> forecastList.stream() .map(CompletionStage::toCompletableFuture) .map(CompletableFuture::join) .collect(Collectors.toList())); }); // ServiceResponse, // // . // future // forecastCS, // CompletableFuture.completedFuture( new ServiceResponse()) .thenCombine(forecastCS, ServiceResponse::forecasts) .whenCompleteAsync((response, throwable) -> { response.setProcessingTime( System.currentTimeMillis() - startTime); async.resume(response); }); } }
ForecastReactiveResource
I first create a client call to location services using the JAX-RS Reactive Client API. As I mentioned above, this is an add-on for Java EE 8, and it helps to create a reactive call simply by using the rx()
method.forecastCS
. Ultimately, I will create a service call response using only forecastCS
.forecastList
. To create a completion stage for each forecast, I transmit the data by location, and then create the tempCS
variable, again using the JAX-RS Reactive Client API, which calls the temperature service with the name of the city. Here, I use the resolveTemplate()
method to build the client, and this allows me to pass the city name to the collector as a parameter.CompletableFuture.completedFuture()
, passing the new Forecast
instance as a parameter. I combine this future with the tempCS
stage so that I have a temperature value for the iterated locations.CompletableFuture.allOf()
method in Listing 11 converts the completion stage list to forecastCS
. Performing this step returns a large instance of a completable future when all the objects provided are completable future.ServiceResponse
class, so I create a completed future, and then combine the forecastCS
completion stage with the list of forecasts and calculate the response time of the service.ForecastReactiveResource
will be similar to that presented in Listing 12. As shown in the output, the processing time is 515 ms, which is the ideal execution time for obtaining temperature values from one location. { "forecasts": [ { "location": { "name": "London" }, "temperature": { "scale": "Celsius", "temperature": 49 } }, { "location": { "name": "Istanbul" }, "temperature": { "scale": "Celsius", "temperature": 32 } }, { "location": { "name": "Prague" }, "temperature": { "scale": "Celsius", "temperature": 45 } } ], "processingTime": 515 }
CompletionStage
and CompletableFuture
classes available in Java 8, the power of asynchronous processing is pulled free by reactive programming.Source: https://habr.com/ru/post/424031/
All Articles