[Java, JavaFX] JavaFX Integration Strategies
Minggu, 24 November 2013
0
komentar
http://www.oracle.com/technetwork/articles/java/javafxinteg-2062777.html
With lambda式と非同期通信のサポートにより、JavaFXはバックエンドサービスの新たな連携の可能性をもたらします。
分断されたアプリケーションを企業内で見つけることはそうそうないでしょう。エンタープライズデスクトップアプリケーションは、アプリケーションサーバが公開する一つ以上のバックエンドサービスのデータをレンダリングし、操作します。古いSwingとJ2EEの時台は、通信は、一方向かつ同期型でした。 JavaFXとJava EE 6およびJava EE 7では、新たな同期、非同期、プッシュ、プル型といった様々なの連係戦略が導入されています。この記事では、JavaFXアプリケーションを使用してJava EEサービスを統合することに焦点を当てます。
JavaFX Is Java
JavaFXはJavaです。そのため、Swingアプリケーションのために使っているベストプラクティスはJavaFXに適用できます。バックエンドサービスとの統合はプロトコルにも技術にも依存しません。サービスには様々な設定(IPアドレス、ポート番号、プロパティファイル)が含まれています。APIのメソッドは、
java.rmi.RemoteException
のようなプロトコル固有の例外をスローするため、無関係な詳細情報でプレゼンテーションロジックを汚すことがあります。プロプライエタリなサービスを囲む薄いラッパーは実装の詳細を隠蔽し、より意味のあるインターフェースを公開します。これは古典的なGoF(Gang of Four)のAdapterパターンです。Adapterパターン
http://en.wikipedia.org/wiki/Adapter_pattern
http://ja.wikipedia.org/wiki/Adapter_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
Revival of the Business Delegate
J2EEクライアントはバックエンドとの通信において、その昔はRMI-IIOP、その後はJAX-RPCやJAX-WSに大きく依存していました。どちらのAPIとも、厳密にチェックされた例外を使い、特定のテクノロジーに結びついています。プレゼンテーションロジックをプロトコルから分離する上で、Business Delegateパターンが必要でした。Core J2EE Patterns - Business Delegate
http://www.oracle.com/technetwork/java/businessdelegate-137562.html
"Use a Business Delegate to reduce coupling between presentation-tier clients and business services. The Business Delegate hides the underlying implementation details of the business service, such as lookup and access details of the EJB architecture."Business Delegateはデフォルトケースでは実際のプロキシ、テスト環境ではMockオブジェクトを作成するファクトリを用いてよく拡張されました。最近のMockライブラリ、例えばMockitoを使うと、Business Delegateを直接Mockにすることができます。
(Business Delegateを使って、プレゼンテーション層クライアントとビジネスサービス間の結合を減らします。Business Delegateは、EJBアーキテクチャの詳細の検索やアクセスのような、基盤となるビジネスサービスの実装の詳細を隠蔽します)
mockito - simpler & better mockingJavaFXおよびJava EEコンテキストにおけるBusiness Delegateを、実装の詳細を隠蔽し、JavaFXに対し使い勝手のよいインターフェースを公開するアダプタPOJOとして実装することができます。
http://code.google.com/p/mockito/
Figure 1 |
First Request, Then Response
ブロッキングリクエストをアプリケーションサーバーに送信し、データの到着を待つというのは、最もシンプルなバックエンドとの統合方法です。Business Delegateはバックエンドと通信するサービスになります(コード1)。コード1import javax.json.JsonObject;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class MethodMonitoring {
private Client client;
public void init() {
this.client = ClientBuilder.newClient();
}
public MethodsStatistics getMethodStatistics(String application, String ejbName) {
final String uri = getUri();
WebTarget target = this.client.target(uri);
Response response = target.
resolveTemplate("application", application).
resolveTemplate("ejb", ejbName).
request(MediaType.APPLICATION_JSON).get(Response.class);
if (response.getStatus() == 204) {
return null;
}
return new MethodsStatistics(response.readEntity(JsonObject.class));
}
}
MethodMonitoring
クラスは実装、テストが簡単で、プレゼンテーション層と統合が可能です。 getMethodStatistics
メソッドは潜在的に無制限の時間ブロックすることが可能であるため、UIリスナー・メソッドからの同期呼び出しを使ってUIが応答しないようになります。Asynchronous Integration
幸いにして、JAX-RS 2.0 APIでは非同期・コールバックベースの通信モデルもサポートしています。ブロッキングの代わりに、getMethodStatistics
メソッドはリクエストを開始し、コールバックを登録します(コード2)コード2コールバックは返ってくるimport javax.json.JsonObject;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import java.util.function.Consumer;
public class MethodMonitoring {
private Client client;
@PostConstruct
public void init() {
this.client = ClientBuilder.newClient();
}
public void getMethodStatistics(Consumer<MethodsStatistics> consumer, Consumer<Throwable> error,
String application, String ejbName) {
final String uri = getUri();
WebTarget target = this.client.target(uri);
target.
resolveTemplate("application", application).
resolveTemplate("ejb", ejbName).
request(MediaType.APPLICATION_JSON).async().get(new InvocationCallback<JsonObject>() {
@Override
public void completed(JsonObject jsonObject) {
consumer.accept(new MethodsStatistics(jsonObject));
}
@Override
public void failed(Throwable throwable) {
error.accept(throwable);
}
});
}
}
JsonObject
をドメインオブジェクトに変換し、それを java.util.function.Consumer
の実装に渡します。Business Delegateの実装はまだJavaFX APIから独立しており、Java 8の java.util.function.Consumer
をコールバックとして利用します。Java 7では、任意のカスタムインターフェースやクラスをコールバックとして使うこことができますが、Java 8ではJavaFXのpresenterを劇的に簡素化することができます(コード3)。コード3...
this.methodMonitoring.getMethodStatistics(s -> onArrival(s),
t -> System.err.println(t), this.monitoredApplication, ejb);
...
void onArrival(MethodsStatistics statistics) {
Platform.runLater(() -> {
this.methodStatistics.clear();
this.methodStatistics.addAll(statistics.all());
}
);
}
java.util.function.Consumer
をlambda式として実装することができます。これにより、コード量を大きく削減できます。JavaFXはシングルスレッドのUIツールキットゆえ、非同期でマルチスレッドからアクセスすることはできません。java.lang.Runnable
インターフェースのlambda実装を Platform.runLate
r メソッドに渡し、後の実行のためにイベントキューに追加しています。Javadocには以下のような記述があります。"Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater."
(将来のある時間に、JavaFXアプリケーションのスレッドで指定されたRunnableを実行します。任意のスレッドから呼び出される可能性があるこのメソッドは、イベントキューにRunnableをエンキューし、呼び出し元にすぐに戻ります。RunnableはQueueに入った順序で実行されます。runLaterメソッドに渡されたRunnableは、runLaterへの後続の呼び出しに渡されたRunnableの前に実行されます)
Platform#runLater
メソッドは長時間実行するタスクには適切ではありません。非同期スレッドからのJavaFX UIコンポーネントのアップデートのためだけに使うべきです。Tasks for Real Work
Platform.runLater
は「重労働」の実装を目的としているのではなく、JavaFXノードの高速なアップデートを目的としています。長時間実行されるメソッドの非同期呼び出しは、スレッドの作成が必要で、これをJavaFXの javafx.concurrent.Task
クラスでネイティブにサポートしています。 Task
は Worker
および EventTarget
インターフェースを実体化し、 java.util.concurrent.FutureTask
クラスから継承するため、JavaのスレッドとJavaFXのイベント機構の間のブリッジと見なすことができます。 Task
を直接 普通の Runnable
として Thread
が利用することも、 Callable
として ExecutorService
に渡すこともできます。どちらの場合も、非同期実行の機能を持たない同期Java EE API、例えばIIOPをBusiness Delegteで最初にラップできます。 コード4次に、ブロッキングBusiness Delegateメソッドをpublic class SnapshotFetcher{
...
public Snapshot getSnapshot() {
return fetchFromServerSynchronously();
}
...
Task
でラップすると、ようやく非同期実行が可能になります。コード5Task<Snapshot> task = new Task<Snapshot>() {
@Override
protected Snapshot call() throws Exception {
SnapshotFetcher fetcher = new SnapshotFetcher();
return fetcher.getSnapshot();
};
Task
は Runnable
であり Future
なので、直接 Thread
が実行することも、 Executor
に渡すこともできます。JavaFXはシームレスにバインド可能なプロパティを使用してUIとスレッドを統合する javafx.concurrent.Service
クラスが付属しています。Service
は、実際には Task
ファクトリです。コード6import javafx.concurrent.Service;
import javafx.concurrent.Task;
import org.lightview.model.Snapshot;
public class SnapshotProvider extends Service<Snapshot> {
@Override
protected Task<Snapshot> createTask() {
return new Task<Snapshot>() {
@Override
protected Snapshot call() throws Exception {
SnapshotFetcher fetcher = new SnapshotFetcher();
return fetcher.getSnapshot();
};
};
}
}
Service
の状態だけでなく Task
の実行結果も、バインド可能なJavaFXプロパティとして利用可能です。コード7this.service = new SnapshotProvider();
service.start();
service.valueProperty().addListener(
(observable,old,newValue) ->{
//process new value
});
Task
クラスは、同期型レガシーリソースの非同期統合、または単にクライアント側で長時間実行プロセスを起動するための便利な手段です。Blocking for Asynchronicity
Cometとロング・ポーリングはブラウザを用いてHTTPベースのプッシュ通信のシミュレーションのための美しくないHackです。CometHTTPはリクエストーレスポンスプロトコルゆえ、レスポンスはリクエストに対する回答として送信することができます。そのため、最初のリクエストがないのにHTTPを介してブラウザにデータをプッシュすることはできません。ロングポーリングの通信スタイルは実装が簡単です。ブラウザは、サーバーがブロックしている接続を開始します。サーバはブロッキング通信を使ってデータをブラウザに送信し、直ちに接続を閉じます。ブラウザはデータを処理し、サーバを使って後続のブロッキング接続を再開します。何も伝えることがない場合、サーバはブラウザに対しリクエスト204を送信します。
http://en.wikipedia.org/wiki/Comet_%28programming%29
それゆえ、HTTP通信に限定されずに、JavaFXアプリケーションはスタンドアロンJavaアプリケーションとして企業内に展開されますが、多くの場合、RESTエンドポイントは、HTML5のクライアント用に利用可能であり、JavaFXアプリケーションが直接再利用することも可能です。RESTおよびJSONは、HTML5クライアントやJavaアプリケーション、そして低レベルデバイスとの通信のための新しい最小公分母になっています。
JavaFXアプリケーションは直接ロングポーリングに参加することができ、HTML5クライアントと同じ方法で通知することができます。同期通信とロング・ポーリングの唯一の違いは、ブロッキング呼び出しを繰り返し開始する点です。定期的なポーリングを直接
javafx.concurrent.Service
で実装できます。実行の成功・失敗にかかわらず、サービスをリセットし、その後再起動します。コード8javafx.concurrent.Service service = ...;
void registerRestarting() {
service.stateProperty().addListener((observable,oldState,newState) -> {
if (newState.equals(Worker.State.SUCCEEDED) ||
newState.equals(Worker.State.FAILED)) {
service.reset();
service.start();
}
});
}
Push Integration
プッシュ通信は、リクエスト部分のないリクエスト・レスポンス型の通信スタイルです。アプリケーション·サーバーは、いつでもデータをプッシュすることができます。Java Message Service(JMS)やWebSocket、メモリ·グリッドは、fire-and-forgetスタイルの通知メカニズムを備えており、簡単にJavaFXと統合することができます。JSR 356はJava EE 7に含まれるWebSocketプロトコルを実装しており、JavaクライアントAPIを備えています。WebSocket仕様では双方向バイナリプロトコルを導入し、UIクライアントとの統合にぴったり適しています。
JSR 356: JavaTM API for WebSocketWebSocketメッセージはバイナリもしくはテキストをとることができ、コード9のように
http://www.jcp.org/en/jsr/detail?id=356
Endpoint
サブクラスを使ってメッセージを受信します。 コード9import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
public class SnapshotEndpoint extends Endpoint {
private SimpleObjectProperty<Snapshot> snapshot;
private final Unmarshaller unmarshaller;
private JAXBContext jaxb;
public SnapshotEndpoint() {
try {
this.jaxb = JAXBContext.newInstance(Snapshot.class);
this.unmarshaller = jaxb.createUnmarshaller();
} catch (JAXBException e) {}
this.snapshot = new SimpleObjectProperty<>();
}
@Override
public void onOpen(Session session, EndpointConfig ec) {
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String message) {
final Snapshot current = deserialize(message);
Platform.runLater(() ->
snapshot.set(current));
}
});
}
Snapshot deserialize(String message) {
try {
return (Snapshot) unmarshaller.unmarshal(new StringReader(message));
} catch (JAXBException e) {}
}
}
SnapshotEndpoint
クラスは文字列のメッセージを受け取り、Java Architecture for XML Binding (JAXB) APIを使って変換します。JSR 222: JavaTM Architecture for XML Binding (JAXB) 2.0
http://jcp.org/en/jsr/detail?id=222
Snapshot
ドメインオブジェクトはアノテーションの付いたPOJOです。コード10JSR 356 APIは拡張機能をサポートしているため、シリアル化と逆シリアル化を専用のクラスに分解することができます。また、我々は、JAXBに限定していないので、JSONやシリアライズといった任意の利用可能なオブジェクト表現を利用できます。@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Snapshot {
//...arbitrary fields
private long id;
}
SnapshotEndpoint
はWebSocketの専用スレッドによってクライアント側で実行されるので、メッセージを使ってUIだけを更新することはできません。Platform.runLater
メソッドを使い、メッセージを適切にWebSocketからJavaFXのスレッドに渡します。Endpoint
は、実際の通信のみを担当しています。さらに、WebSocketのコンテナは、専用クラスに実装されているものに対する初期設定と初期化が必要です。コード11これまでのところ、我々はほとんどJavaFXの統合機能を使用していません。その代わりに、様々な統合のスタイルに焦点を当ててきました。しかし、JavaFXのプロパティを使うと、同期および非同期の統合が、特に興味深いものになります。public class SnapshotSocketListener {
private SnapshotEndpoint endpoint;
private Session session;
public SnapshotSocketListener() {
this.endpoint = new SnapshotEndpoint();
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
String uri = "ws://localhost:8080/lightfish/snapshots/";
try {
session = container.
connectToServer(this.endpoint, config, URI.create(uri));
} catch (DeploymentException | IOException e) {
throw new IllegalStateException("Cannot connect to WebSocket: ", e);
}
}
public ReadOnlyObjectProperty<Snapshot> snapshotProperty() {
return endpoint.snapshotProperty();
}
@PreDestroy
public void disconnect() {
try {
session.close();
} catch (IOException e) {
}
}
}
Integration the JavaFX Way
javafx.beans.property.ObjectProperty
は Object
インスタンスをラップしており、バインド可能です。関心のあるクライアントがリスナーとして登録したり、直接プロパティにバインドしたりすると、ラップされたインスタンスが置き換わった場合に通知を受け取ることができます(Java Magazineの記事"JavaFX Data Binding for Enterprise Applications"の"Binding for Narrow Interfaces"の章を参照してください)。JavaFX Data Binding for Enterprise Applications通信プロトコルや同期・非同期に関係なく、レスポンスは、UIが表示するドメインオブジェクトを伝達します。
http://www.oraclejavamagazine-digital.com/javamagazine/20130910?pg=53&pm=1&u1=texterity&linkImageSrc=/javamagazine/20130910/data/imgpages/tn/0053_rzzace.gif/%20-%20pg53#pg53
ObjectProperty
を使って、UIは直接値とバインドすることができ、データ到着時には自動的に通知を受け取ることができます。プレゼンターは直接ObjectProperty
にバインドするので、追加の管理手法は不要です。コード12バインド可能なthis.snapshotSocketListener.snapshotProperty().
addListener((o, oldValue, newValue) -> {
snapshots.add(newValue);
onSnapshotArrival(newValue);
});
ObjectProperty
を使うと、大幅にシンプルになり、インターフェイスが顕著に「狭く」なります。バインディングをアウトバウンド通信に適用することもできます。プレゼンターは、ドメインオブジェクトもしくはモデルの状態を変更すると、サービス(Business Delegate)の通知を引き起こします。アウトバウンド通信は、UIスレッドとの同期は不要です。非同期操作も直接UIスレッドから実行することができ、長時間実行する操作を javafx.concurrent.Service
でラップすることも、Business Delegate内で非同期実行することもできます。しかし、すべてのUI操作がドメインオブジェクトの状態を変更するわけではありません。「保存」や「リフレッシュ」といった簡単なユーザーアクションをBusiness delegateメソッド呼び出しに直接変換することができます。One Step Further for Responsiveness and Simplicity
JavaFXのUIはイベント駆動で非同期です。また、JAX-RS 2.0や、JMS、WebSocketなどのJava EE 7 APIは非同期機能を有しています。JavaFXを非同期のJava EE 7 APIと一緒に使うと、大幅にコードがシンプルになります。すべてのBusiness Delegateの操作は、UIあるいはBusiness Delegate自体をブロックせずに非同期に実行することができます。インタラクションパターンは、通信プロトコル非依存であり、すべての非同期Java EE 7 APIで一貫して適用できます。リクエストは、 "fire-and-forget"方式でサーバーに送信されます。応答を待つのではなく、コールバックメソッドを応答処理のために登録します。コールバックは、データを受信し、ドメインオブジェクトを移入し、
Platform.runLater
メソッド内の ObjectProperty#set
を使って現在のドメインオブジェクトを置き換えます。ドメインオブジェクトへの変更は、プレゼンターに伝達されます。このとき、任意の関心のあるビューに対する変更をマルチキャストで通知します。完全非同期通信にすると、大幅に必要なコードの量を減らすことができます。データバインディングを伴う双方向fire-and-forgetアプローチを使用すると、サーバ側での一時的なモデルとマスタ状態との間でデータの同期が不要になります。すべてのアクションは、直接Business Delegateに渡され、アプリケーションサーバからのすべてのレスポンスによって直接UIが更新されます。
また、完全非同期のインタラクションにすると、ユーザーエクスペリエンス(UX)を大幅に改善することができます。サーバー側のインタラクションがどれほど高コストであったとしても、UIがブロックされることはありません。
結論
一見すると、バックエンドサービスとのJavaFXの統合はSwingに非常に似ています。Platform#runLater
は javax.swing.SwingUtilities#invokeLater
と同等であり、 javafx.concurrent.Service
の目的と javax.swing.SwingWorker
は類似しています。近代的なJava EE7 APIやJavaFXデータ・バインディング("JavaFX Data Binding for Enterprise Applications"をご覧下さい)、FXMLとScene Builderを使った制御機能の反転("Integrating JavaFX Scene Builder into Enterprise Applications"をご覧下さい)を使うと、大幅にプレゼンテーションロジックを簡素化し、マルチビューデスクトップアプリケーションを実装するための一貫したアプローチを導入することができます。
JavaFX Data Binding for Enterprise ApplicationsJava EE 6およびJava EE 7ベースのバックエンドシステムであれば、プレゼンテーション層だけでなく、サーバーサイドでも継続して非同期通信のスタイルを利用することができます。
http://www.oraclejavamagazine-digital.com/javamagazine/20130910?pg=53&pm=1&u1=texterity&linkImageSrc=/javamagazine/20130910/data/imgpages/tn/0053_rzzace.gif/#pg53
Integrating JavaFX Scene Builder into Enterprise Applications
http://www.oraclejavamagazine-digital.com/javamagazine/20130506/?pg=75&pm=1&u1=friend%20-%20pg75#pg75
参考資料
- Factory Pattern
http://en.wikipedia.org/wiki/Factory_method_pattern - Unit Testing for Java EE
http://www.oracle.com/technetwork/articles/java/unittesting-455385.html - LightFish/LightView
http://lightfish.adam-bien.com/ - Airpad
http://airpad.adam-bien.com/ - Integrating JavaFX Scene Builder into Enterprise Applications (Java Magazine)
http://www.oraclejavamagazine-digital.com/javamagazine/20130506/?pg=75&pm=1&u1=friend#pg75 - JavaFX Data Binding for Enterprise Applications (Java Magazine)
http://www.oraclejavamagazine-digital.com/javamagazine/20130910?pg=53&pm=1&u1=texterity&linkImageSrc=/javamagazine/20130910/data/imgpages/tn/0053_rzzace.gif/#pg53
著者について
コンサルタントである著者のAdam Bienは、Java EE 6、Java EE 7、EJB 3.x、JAX-RS、JPA 2.x JSRのExpert Groupメンバーです。Adam Bien's BlogJDK 1.0の頃からJavaテクノロジーに関わり、Servlet/EJB 1.0を経て、現在はJava SEおよびJava EEプロジェクトのアーキテクトであり、開発者です。Java FX、J2EE、Java EEに関する書籍を執筆しており、Real World Java EE Patterns - Rethinking Best PracticesとReal World Java EE Night Hacks - Dissecting the Business Tierの著者でもあります。
http://www.adam-bien.com/roller/abien/
Real World Java EE Patterns—Rethinking Best PracticesAdamはJava Championにして、Top Java Ambassador 2012、JavaOne 2009、2011、2012 Rock Starでもあります。Adamはミュンヘン空港でのJava EEワークショップを時々開催しています。
http://realworldpatterns.com/
Real World Java EE Night Hacks—Dissecting the Business Tier
http://press.adam-bien.com/real-world-java-ee-night-hacks-dissecting-the-business-tier.htm
Java EE Workshops with Adam Bien at MUC Airport
http://airhacks.com
TERIMA KASIH ATAS KUNJUNGAN SAUDARA
Judul: [Java, JavaFX] JavaFX Integration Strategies
Ditulis oleh Unknown
Rating Blog 5 dari 5
Semoga artikel ini bermanfaat bagi saudara. Jika ingin mengutip, baik itu sebagian atau keseluruhan dari isi artikel ini harap menyertakan link dofollow ke https://apk-zipalign.blogspot.com/2013/11/java-javafx-javafx-integration.html. Terima kasih sudah singgah membaca artikel ini.Ditulis oleh Unknown
Rating Blog 5 dari 5
0 komentar:
Posting Komentar