Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

GWT, Java 8 и Future

Anna | 2.06.2014 | нет комментариев
Добрый день.
Думаю, многие из вас знают о выходе 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<Void> callback);

взаимодействие с сервером будет выглядеть так:

Future<Void> makeServerRequest();

Мне показалась дюже симпатичной реализация Future в Scala, и захотелось сделать что-то сходственное. Вот что получилось:

Интерфейс

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

}
Имплементация

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) -> {
            if (result.isSuccess()) {
                Future<R> f = function.apply(result.get());
                f.onComplete(mapped::completeWithResult);
            }
        });
        return mapped;
    }

    @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<List<Country>> callback);

    @GET
    @Path("/regions/{countryId}/")
    public void getRegions(@PathParam("countryId") Integer countryId, MethodCallback<List<Region>> callback);

}

Вот несколько примеров применения этого обслуживания с использованием Future и без него:

  1. Самый примитивный пример. Мы хотим взять список стран и отобразить его в нашем View:
    Без Future:

    countryService.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);
    	}
    });
    

    С Future:

    countryService.getCountries().forEach(view::displayCountries);	
    

    Способ forEach это своего рода onSuccess callback’a. То есть при удачном выполнении вызовется способ displayCountries у View.

  2. Пример потруднее. Возможен, нам необходимо обработать исключение и отобразить его.
    Без Future:

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

    С Future:

    countryService.getCountries().handle(t -> view.displayError(t.getMessage()), view::displayCountries);
    

    В способ Future.handle мы передаем две функции. Одна отвечает за обработку ошибки, вторая за обработку удачного выполнения с итогом.

  3. Нам необходимо взять первую страну из списка и отобразить список регионов для нее:
    Без Future:

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

    С применением Future:

    countryService.getCountries()
        .map(countries -> countries.get(0).getId())
        .flatMap(countryService::getRegions)
        .handle(err -> view.displayError(err.getMessage()), view::displayRegions);
    

    Вначале мы конвертируем Future<List<Country>> в Future<Integer>, это вернет нам countryId при удачном выполнении. После этого получаем Future со списком регионов. И, наконец, обрабатываем итог.

  4. Нам необходимо получить все регионы всех стран.
    Решение такой задачи без применения Future является достаточно массивным. Следственно приведу сразу 2-й вариант.

    Разобьем задачу на несколько этапов:

    Future<List<Future<List<Region>>>> regionFutures = countryService.getCountries()
        .map(
                countries ->
                        countries.map(country -> countryService.getRegions(country.getId()))
        );
    

    Тут мы получаем список Future всех регионов.

    В дальнейшей трансформации нужно привести List<Future<R>> к Future<List<R>>. То есть наш Future выполнится тогда, когда все Future внутри списка будут закончены.

    Future<Future<List<List<Region>>>> regions = regionFutures.map(FutureUtils::toFutureOfList);
    

    И, наконец, приводим Future<Future<R>> к Future<R>, а так же трансформируем список списков в одномерный список:

    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-способах.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB