GWT、Java 8、および将来

こんにちは

皆さんの多くは、Java 8のリリースと、Java 8がもたらす革新について知っていると思います。 残念ながら、現時点の最新バージョンのGWT(2.6.0)は、ラムダとインターフェースのデフォルトメソッドをまだサポートしていません。 GWTフレームワークは非常に需要が高いため、多くの場合、その開発に対処する必要があるため、言語に追加された機能を使用してGWTで記述しようと熱望していました。



この記事では、GWTでJava 8機能のサポートを追加する方法と、実際にこれがすべて必要な理由について、Futureサンプルを使用して説明します。 GWTを使用したことがある場合は、サーバーにアクセスするときにコールバックに関連するすべての欠点と不便さを示します。 javascriptの世界では多くの人が長い間Future / Promiseを使用しており、一部の言語ではこの概念は標準ライブラリに組み込まれていますが、GWTはクライアントとサーバー間のやり取りの方法でコールバックを使用します(RemoTeServiceServlet、RPC-Dispatch)またはRequestFactory。

それでは始めましょう。



GWTを収集します



短い検索の後、 GWTの実験的な分岐が見つかりました。 Java 8のかなりまともなサポートを主張しています(JREエミュレーションライブラリを除く)。 実際、これはそうではありませんでした。 このフォークで使用されているjdt-coreバージョンは非常に古く、通常のキャストができません。 バージョンを3.9.5に上げる必要があり、利点を少し編集する必要がありました(一部のメソッドシグネチャのみが変更されました)。



完了、gwt-dev.jar、gwt-user.jar、gwt-codeserver.jarライブラリがgwt-sandbox / build / libディレクトリに現れました。



RestyGWTを修正





この例では、変更されたRestyGWTライブラリを使用します。

FutureサポートのあるRestyGWTは次のとおりです。



代わりに

 void makeServerRequest(MethodCallback<Void> callback);
      
      





サーバーとの対話は次のようになります。

 Future<Void> makeServerRequest();
      
      







ScalaでのFutureの実装は私にとって非常に魅力的でした。 起こったことは次のとおりです。

インターフェース
 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. 最も単純な例。 国のリストを取得して、ビューに表示します。

    未来なし:



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







    未来とともに:



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





    forEachメソッドは、一種のonSuccessコールバックです。 つまり、実行が成功すると、ViewのdisplayCountriesメソッドが呼び出されます。



  2. 例はもっと複雑です。 例外を処理して表示する必要があるとします。

    未来なし:



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







    未来とともに:



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





    Future.handle



    メソッドに2つの関数を渡します。 1つはエラーの処理を担当し、2つ目は結果の実行の成功を処理します。



  3. リストから最初の国を取得し、その地域のリストを表示する必要があります。

    未来なし:



     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> Future, countryId . Future . , , .





    .

    Future . .



    :











    変換しFuture<List> Future, countryId . Future . , , .





    .

    Future . .



    :











    Future<List> Future, countryId . Future . , , .









    .

    Future . .



    :







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





    ここで、すべての地域のFutureのリストを取得します。



    次の変革では、
     List<Future>  Future<List>.    Future  ,   Future    . 
          



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








    そして最後に、私たちは与える
     Future<Future>  Future,         : 
          



    FutureUtils .flatten(regions) .map(ListUtils::flatten) .handle(err -> view.displayError(err.getMessage()), view::displayRegions());








    後者の例の欠点は、このコードの作成に参加しなかった人にとって理解するのがかなり難しいことです。 しかし、ソリューションは非常にコンパクトであることが判明し、存在する権利があります。







    PS GWTでJava 8を試してみたい人のために、記事の例を含むデモプロジェクトが用意されています。 プロジェクトはmavenezedです。mvnjetty:run-explodedから開始できます。



    これまでのところ、提供されているすべてのライブラリを実際のプロジェクトで使用しない方がよいことを理解しておく必要があります。 RestyGWTでの将来のサポートはかなり粗雑で、まだテストされておらず、たとえばJSONPリクエストで動作することはできません。 デフォルトのインターフェースとラムダのサポートは非​​常に自信を持って機能しますが、静的メソッドでラムダを使用する場合、コンパイルは常に機能するとは限りません。



All Articles