GWT, Java 8 and Future
Hello.
I think many of you know about the release of Java 8, and what innovations it brings. Unfortunately, the latest version of GWT(2.6.0) at the moment still does not support lambdas and default methods in interfaces. Because the GWT framework is quite popular, many people often have to face the development on it, I was eager to try to write in GWT using the newly added language features.
In this article we will talk about how to add support for Java 8 features in GWT, and about what, actually, is all this necessary — for example the Future. If you have ever worked with GWT, it seems all the shortcomings and inconvenience as AMI when accessing the server. At that time, as in the javascript world many are already using Future/Promise, and in some languages this concept is built into the standard library in GWT are still used callbacks in any kind of interaction between client and server, whether RemoTeServiceServlet, RPC or RequestFactory Dispatch.
So, let's start.
After a short search, was found experimental fork of GWT. It is stated quite tolerable support for Java 8 (except for the JRE Emulation Library). In fact, it was not so. The version of jdt-core, which is used in this fork, is quite old and not able to properly lead types. Had to raise the version to 3.9.5, the benefit rule should have been a little (only changed some method signatures).
the
Ready, in the directory of gwt-sandbox/build/lib libraries were established gwt-dev.jar, gwt-user.jar, gwt-codeserver.jar.
For our example we will use a modified library RestyGWT.
Here is RestyGWT-enabled Future.
Now, instead
the
the interaction with the server would look like this:
the
It seemed to me a very attractive implementation of Future in Scala, and wanted to do something like that. Here's what happened:
What we all did? I'll try to explain what is called "fingers".
Let's say we have a service to retrieve a list of countries and regions:
the
Here are a few examples of usage of this service with the use of the Future without him:
Article based on information from habrahabr.ru
I think many of you know about the release of Java 8, and what innovations it brings. Unfortunately, the latest version of GWT(2.6.0) at the moment still does not support lambdas and default methods in interfaces. Because the GWT framework is quite popular, many people often have to face the development on it, I was eager to try to write in GWT using the newly added language features.
In this article we will talk about how to add support for Java 8 features in GWT, and about what, actually, is all this necessary — for example the Future. If you have ever worked with GWT, it seems all the shortcomings and inconvenience as AMI when accessing the server. At that time, as in the javascript world many are already using Future/Promise, and in some languages this concept is built into the standard library in GWT are still used callbacks in any kind of interaction between client and server, whether RemoTeServiceServlet, RPC or RequestFactory Dispatch.
So, let's start.
Sobrien GWT
After a short search, was found experimental fork of GWT. It is stated quite tolerable support for Java 8 (except for the JRE Emulation Library). In fact, it was not so. The version of jdt-core, which is used in this fork, is quite old and not able to properly lead types. Had to raise the version to 3.9.5, the benefit rule should have been a little (only changed some method signatures).
the
-
the
- so, take the source code for gwt here and the gwt-tools here. the
- After cloning, you must specify the environment variable
GWT_TOOLS=path/to/gwt-tools.
the - Then go to the installation directory of the GWT source and run ant build.
Ready, in the directory of gwt-sandbox/build/lib libraries were established gwt-dev.jar, gwt-user.jar, gwt-codeserver.jar.
Run RestyGWT
For our example we will use a modified library RestyGWT.
Here is RestyGWT-enabled Future.
Now, instead
the
void makeServerRequest(MethodCallback<Void> callback);
the interaction with the server would look like this:
the
Future<Void> makeServerRequest();
It seemed to me a very attractive implementation of Future in Scala, and wanted to do something like that. Here's what happened:
Interface
public interface Future<T> {
public void onComplete(Consumer<Try<T>> consumer);
public void handle(Consumer < Throwable > errorHandler, Consumer<T> successHandler);
public void forEach(Consumer < T > consumer);
public <R> Future<R> map(Function < T, R > function);
public <R> Future<R> flatMap(Function < T, Future<R>> function);
public T get();
}
Implementation
public class FutureImpl<T> implements Future<T> {
private List<Consumer<Try<T>>> completeFunctions = new ArrayList<>();
private Option<Try<T>> result = Option.getEmpty();
public FutureImpl() {
}
@Override
public void onComplete(Consumer<Try<T>> consumer) {
result.forEach(consumer::accept);
completeFunctions.add(consumer);
}
@Override
public void handle(Consumer < Throwable > errorHandler, Consumer<T> successHandler) {
onComplete((result) -> {
if (result.isSuccess()) successHandler.accept(result.get());
else errorHandler.accept(result.getCause());
});
}
public void completeWithResult(Try<T> result) {
this.result = Option.create(result);
for (Consumer<Try<T>> completeFunction : completeFunctions) {
completeFunction.accept(result);
}
}
public void completeWithSuccess(T result) {
completeWithResult(new Success<T>(result));
}
public void completeWithFailure(Throwable ex) {
completeWithResult(new Failure<T>(ex));
}
@Override
public void forEach(Consumer < T > consumer) {
onComplete((result) -> {
if (result.isSuccess()) {
consumer.accept(result.get());
}
});
}
@Override
public <R> Future<R> map(Function < T, R > function) {
FutureImpl<R> future = new FutureImpl<R>();
onComplete((result) -> {
if (result.isSuccess()) {
future.completeWithSuccess(function.apply(result.get()));
}
});
return future;
}
@Override
public <R> FutureImpl<R> flatMap(Function < T, Future<R>> function) {
FutureImpl<R> mapped = new FutureImpl<R>();
onComplete((result) -> {
Future<R> f = function.apply(result.get());
f.onComplete(mapped::completeWithResult);
}
});
return mapped;
}
@Override
public T get() {
return result.get().get();
}
}
Usage
What we all did? I'll try to explain what is called "fingers".
Let's say we have a service to retrieve a list of countries and regions:
the
@Path("../service")
@Consumes(MediaType.APPLICATION_JSON)
public interface CallbackCountryService extends RestService {
@GET
@Path("/countires/")
public void getCountries(MethodCallback<List<Country>> callback);
@GET
@Path("/regions/{countryId}/")
public void getRegions(@PathParam("countryId") Integer countryId, MethodCallback<List<Region>> callback);
}
Here are a few examples of usage of this service with the use of the Future without him:
-
the
- With a simple example. We want to take a list of countries and display it in our View:
No Future:
thecountryService.getCountries(new MethodCallback<List<Country>>() { @Override public void onFailure(Method Method, Throwable exception) { } @Override public void onSuccess(Method Method, List<Country> response) { view.displayCountries(response); } });
With Future:
thecountryService.getCountries().forEach(view::displayCountries);
The forEach method is a kind of onSuccess as a. That is, if successful, will be called method displayCountries from View.
- We need to take the first country from the list and display the list of regions for her:
No Future:
thecountryService.getCountries(new MethodCallback<List<Country>>() { @Override public void onFailure(Method Method, Throwable exception) { view.displayError(exception.getMessage()); } @Override public void onSuccess(Method Method, List<Country> response) { countryService.getRegions(response.get(0).getId(), new MethodCallback<List<Region>>() { @Override public void onFailure(Method Method, Throwable exception) { view.displayError(exception.getMessage()); } @Override public void onSuccess(Method Method, List<Region> response) { view.displayRegions(response); } }); } });
Using the Future:
thecountryService.getCountries() .map(countries - > countries.get(0).getId()) .flatMap(countryService::getRegions) .handle(err -> view.displayError(err.getMessage()), view::displayRegions);
First we converta
Future<List> in the Future, it will return countryId if it succeeds. Then we get Future with a list of regions. Finally, the processed result.
We need to get all regions of all countries.
The solution of this problem without the use of the Future is rather cumbersome. So here is a second option.
Break down the task into several stages:
theFuture<List<Future<List<Region>>>> regionFutures = countryService.getCountries() .map( countries -> countries.map(country -> countryService.getRegions(country.getId())) );
Here we get a list of Future all regions.
The following transformation should allow theList<Future> Future<List>. That is, our Future will be executed when all of the Future within the list will be completed.
Future<Future<List<List<Region>>>> regions = regionFutures.map(FutureUtils::toFutureOfList);
And finally, givenFuture<Future> to Future and transform a list of lists to a one-dimensional list:
FutureUtils .flatten(regions) .map(ListUtils::flatten) .handle(err -> view.displayError(err.getMessage()), view::displayRegions());
The disadvantage of the last example is that it is quite difficult to understand to a person who was not involved in writing this code. However, the solution is quite compact and has a right to exist.
PS For those who want to try Java 8 in GWT, prepared sample project with examples from the article. The project laveneziana, it can be run using mvn jetty:run-exploded.
It should be understood that all of the supplied libraries is better not to use in real projects. Support Future in RestyGWT is quite crude, not yet tested and not yet able to work, for example with JSONP requests. The support for default interfaces and lambda works quite confidently, although the compilation does not always go when you use lambdas in static methods.
a more complex Example. Suppose we want to handle the exception and display it.
No Future:
the
countryService.getCountries(new MethodCallback<List<Country>>() {
@Override
public void onFailure(Method Method, Throwable exception) {
view.displayError(exception.getMessage());
}
@Override
public void onSuccess(Method Method, List<Country> response) {
view.displayCountries(response);
}
});
With Future:
the
countryService.getCountries().handle(t -> view.displayError(t.getMessage()), view::displayCountries);
In the method
Future.handle
we pass two functions. One is responsible for error processing, the second processing success result.the
Комментарии
Отправить комментарий