[Из песочницы] GWT, Java 8 и Future
Добрый день.Думаю, многие из вас знают о выходе Java 8, и о том, какие нововведения она несет. К сожалению, последняя версия GWT (2.6.0) на данный момент до сих пор не поддерживает лямбды и default-методы в интерфейсах. Поскольку фреймворк GWT довольно востребован, многим приходится часто сталкиваться с разработкой именно на нем, мне не терпелось попробовать писать на GWT с использованием вновь добавленных в язык фич.В этой статье речь пойдет о том, как добавить поддержку Java 8 фич в GWT, а так же о том, для чего, собственно, все это нужно — на примере использования Future. Если вы когда-либо работали с GWT, то представляете все недостатки и неудобства, связанные с callback’ами при обращении к серверу. В то время, как в мире javascript многие уже давно используют Future/Promise, а в некоторых языках эта концепция встроена в стандартную библиотеку, в GWT до сих пор используются callbacks в любых способах взаимодействия между клиентом и сервером, будь то RemoTeServiceServlet, RPC-Dispatch или RequestFactory.Итак, приступим.Собриаем GWT После недолгого поиска был найден экспериментальный форк GWT. В нем заявлена довольно сносная поддержка Java 8 (за исключением JRE Emulation Library). На деле это оказалось не совсем так. Версия jdt-core, которая используется в этом форке, довольно старая и не способна нормально приводить типы. Пришлось поднять версию до 3.9.5, благо править надо было немного (поменялись лишь некоторые сигнатуры методов).Итак, берем исходники gwt отсюда и gwt-tools отсюда. После клонирования необходимо прописать переменную окружения GWT_TOOLS=path/to/gwt-tools. Далее идем в директорию с исходниками GWT и запускаем ant-build. Готово, в директории gwt-sandbox/build/lib появились библиотеки gwt-dev.jar, gwt-user.jar, gwt-codeserver.jar.Правим RestyGWT Для нашего примера будем использовать модифицированную библиотеку RestyGWT.Здесь находится RestyGWT с поддержкой Future.
Теперь вместо
void makeServerRequest (MethodCallback
Интерфейс
public interface Future
public void onComplete (Consumer
public void handle (Consumer
public void forEach (Consumer
public
public
public T get ();
}
Имплементация
public class FutureImpl
private List
public FutureImpl () { }
@Override
public void onComplete (Consumer
@Override
public void handle (Consumer
public void completeWithResult (Try
public void completeWithSuccess (T result) {
completeWithResult (new Success
public void completeWithFailure (Throwable ex) {
completeWithResult (new Failure
@Override
public void forEach (Consumer
@Override
public
@Override
public
@Override public T get () { return result.get ().get (); }
} Использование Для чего мы все это проделали? Попробую объяснить, что называется, «на пальцах».Допустим, у нас есть сервис для получения списка стран и регионов: @Path (»…/service») @Consumes (MediaType.APPLICATION_JSON) public interface CallbackCountryService extends RestService {
@GET
@Path (»/countires/»)
public void getCountries (MethodCallback> callback);
@GET
@Path (»/regions/{countryId}/»)
public void getRegions (@PathParam («countryId») Integer countryId, MethodCallback> callback);
} Вот несколько примеров использования этого сервиса с применением Future и без него:
Самый простой пример. Мы хотим взять список стран и отобразить его в нашем View: Без Future:
countryService.getCountries (new MethodCallback>() {
@Override public void onFailure (Method method, Throwable exception) {
}
@Override
public void onSuccess (Method method, List
countryService.getCountries ().forEach (view: displayCountries);
Метод forEach это своего рода onSuccess callback’a. То есть при успешном выполнении вызовется метод displayCountries у View.
Пример посложнее. Допустим, нам нужно обработать исключение и отобразить его.Без Future:
countryService.getCountries (new MethodCallback>() {
@Override public void onFailure (Method method, Throwable exception) { view.displayError (exception.getMessage ()); }
@Override
public void onSuccess (Method method, List
countryService.getCountries ().handle (t → view.displayError (t.getMessage ()), view: displayCountries);
В метод Future.handle мы передаем две функции. Одна отвечает за обработку ошибки, вторая за обработку успешного выполнения с результатом.
Нам нужно взять первую страну из списка и отобразить список регионов для нее: Без Future:
countryService.getCountries (new MethodCallback>() {
@Override
public void onFailure (Method method, Throwable exception) {
view.displayError (exception.getMessage ());
}
@Override
public void onSuccess (Method method, List>() {
@Override public void onFailure (Method method, Throwable exception) { view.displayError (exception.getMessage ()); }
@Override
public void onSuccess (Method method, List> в Future
Future>>> regionFutures = countryService.getCountries ()
.map (
countries →
countries.map (country → countryService.getRegions (country.getId ()))
);
Здесь мы получаем список Future всех регионов.В следующей трансформации надо привести List
>. То есть наш Future выполнится тогда, когда все Future внутри списка будут завершены.
Future
FutureUtils .flatten (regions) .map (ListUtils: flatten) .handle (err → view.displayError (err.getMessage ()), view: displayRegions ()); Недостаток последнего примера в том, что его довольно трудно понять человеку, который не принимал участия в написании этого кода. Однако решение получилось довольно компактным и имеет право на существование.
P.S. Для тех, кто хочет попробовать Java 8 в GWT, подготовлен демонстрационный проект с примерами из статьи. Проект мавенезирован, запускать можно через mvn jetty: run-exploded.Следует понимать, что все предоставленные библиотеки пока лучше не использовать в реальных проектах. Поддержка Future в RestyGWT довольно сырая, еще не оттестирована, и пока не умеет работать, например, с JSONP запросами. Поддержка же default интерфейсов и lambda работает довольно уверенно, хотя компиляция не всегда проходит при использовании лямбд в static-методах.