JavaFX Integration Strategies
by Adam Bien
Published November 2013
With lambdas and support for asynchronous communication, JavaFX introduces new integration possibilities for back-end services.
You will rarely find isolated applications in the enterprise. An enterprise desktop application renders and manipulates the data of one or more back-end services exposed by an application server. In the old Swing and J2EE days, the communication was unidirectional and synchronous. JavaFX and Java EE 6 and 7 introduced a variety of new synchronous, asynchronous, push, and pull integration strategies. This article focuses on the integration of Java EE services with JavaFX applications.
JavaFX Is Java
JavaFX is Java. Thus, the best practices you use for Swing applications, you can apply to JavaFX. The integration of back-end services is both protocol- and technology-agnostic.
Services involve various configurations such as IP addresses, ports, and property files. And the methods of the APIs often throw protocol-specific exceptions such as java.rmi.RemoteException
and, therefore, pollute the presentation logic with irrelevant detail. A thin wrapper around the proprietary service encapsulates the implementation details and exposes a more meaningful interface. It's a classic Gang of Four (GoF) Adapter pattern.
Revival of the Business Delegate
J2EE clients heavily relied on Remote Method Invocation over Internet Inter-ORB Protocol (RMI-IIOP) communication and, later, on Java API for XML-based RPC (JAX-RPC) and Java API for XML Web Services (JAX-WS) with back-end services. Both APIs use heavily checked exceptions and are tied to the particular technology. The Business Delegate pattern was necessary to decouple the presentation logic from the protocol:
"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."
A Business Delegate was often extended with a factory that created real proxies in default case and mock objects in test environments. With modern mock libraries, such as Mockito, a Business Delegate can be directly mocked out. A Business Delegate in a JavaFX and Java EE context can be implemented as an adapter Plain Old Java Object (POJO) that encapsulates the implementation details and exposes a convenient interface to JavaFX.
Figure 1
First Request, Then Response
Sending a blocking request to an application server and then waiting for the data to arrive is the simplest possible integration with the back end. The Business Delegate becomes a service that communicates with the back end, as shown in Listing 1:
import 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));
}
}
Listing 1
The MethodMonitoring
class is easy to implement and test and can be integrated with a presenter. Because the method getMethodStatistics
can potentially block for an unlimited amount of time, a synchronous invocation from a UI listener method would make the UI unresponsive.
Asynchronous Integration
Fortunately, the JAX-RS 2.0 API also supports an asynchronous, callback-based communication model. Instead of blocking, the method getMethodStatistics
initiates the request and registers a callback (see Listing 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);
}
});
}
}
Listing 2
The callback transforms the incoming JsonObject
into a domain object and passes it to the implementation of a java.util.function.Consumer
. The implementation of the Business Delegate is still independent from the JavaFX API and uses the Java 8 java.util.function.Consumer
as a callback. With Java 7, any custom interface or class could be used as a callback. However, with Java 8, the realization of the JavaFX presenter can be drastically simplified, as shown in Listing 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());
}
);
}
Listing 3
The java.util.function.Consumer
can be implemented as a lambda expression, which greatly reduces the amount of code. JavaFX is a single-threaded UI toolkit, so it cannot be accessed by multiple threads asynchronously.
A lambda implementation of the java.lang.Runnable
interface is passed to the Platform.runLate
r method and added to the event queue for later execution. According to the 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."
The Platform#runLater
method is not suitable for the execution of long-running tasks; rather, it is just for the updates of JavaFX UI components from an asynchronous thread.
Tasks for Real Work
Platform.runLater
is not meant for the implementation of "heavy lifting"; rather, it is for fast updates of the JavaFX nodes. Asynchronous invocation of long-running methods requires the creation of threads and is natively supported by JavaFX with the javafx.concurrent.Task
class. A Task
realizes the Worker
and EventTarget
interfaces, inherits from the java.util.concurrent.FutureTask
class, and can be considered a bridge between Java threading and the JavaFX event mechanism. Task
can be either directly used by a Thread
as an ordinary Runnable
or passed to an ExecutorService
as a Callable
. In either case, synchronous Java EE APIs without the ability of asynchronous execution, for example, IIOP, could be wrapped with a Business Delegate first:
public class SnapshotFetcher{
...
public Snapshot getSnapshot() {
return fetchFromServerSynchronously();
}
...
Listing 4
In the next step, the blocking Business Delegate method is wrapped with a Task
and is now able to be executed asynchronously.
Task<Snapshot> task = new Task<Snapshot>() {
@Override
protected Snapshot call() throws Exception {
SnapshotFetcher fetcher = new SnapshotFetcher();
return fetcher.getSnapshot();
};
Listing 5
A Task
is a Runnable
and a Future
, so it could be executed either directly by a Thread
or submitted to an Executor
. JavaFX comes with a javafx.concurrent.Service
class that seamlessly integrates threading with the UI using bindable properties. A Service
is actually a Task
factory:
import 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();
};
};
}
}
Listing 6
The state of Service
as well as the Task
execution results become available as bindable JavaFX properties:
this.service = new SnapshotProvider();
service.start();
service.valueProperty().addListener(
(observable,old,newValue) ->{
//process new value
});
Listing 7
The Task
class is a convenient vehicle for asynchronous integration of synchronous legacy resources or just for the execution of long-running processes at the client side.
Blocking for Asynchronicity
Comet and long polling are ugly hacks for the simulation of push HTTP-based communication with browsers. HTTP is a request-response protocol, so a response can be sent only as an answer to a request. Pushing data to a browser over HTTP without an initial request is, therefore, impossible. Long-polling communication style is easy to implement: the browser initiates a connection that is blocked by the server. The server uses the blocking connection to send data to the browser, which immediately closes the connection. The browser processes the data and reinitiates a subsequent blocking connection with the server. If there is nothing to say, the server sends a 204 request to the browser.
JavaFX applications are deployed in the enterprise as standalone Java applications and, therefore, not limited to HTTP communication. However, often REST endpoints are available for HTML5 clients as well and could be directly reused by JavaFX applications. REST and JSON become the new, least common denominator for communication with HTML5 clients, Java applications, and even low-level devices.
JavaFX applications can directly participate in long polling and can be notified in the same way as HTML5 clients. The only difference between synchronous communication and long polling is the repetitive initiation of a blocking call. The recurring polling can be directly implemented with javafx.concurrent.Service
. Regardless of whether the execution fails or succeeds, the service will be reset and then restarted:
javafx.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();
}
});
}
Listing 8
Push Integration
Push communication is a request-response style of communication without the request part; the application server can push out data at any time. Java Message Service (JMS), WebSockets, and memory grids come with a fire-and-forget style notification mechanism and can be easily integrated with JavaFX.
JSR 356 implements the WebSocket protocol, is part of Java EE 7, and comes with a Java client API. The WebSocket specification introduces a bidirectional binary protocol and is perfectly suitable for the integration of UI clients. WebSocket messages can have a binary or text nature and are received with an Endpoint
subclass, as shown in Listing 9:
import 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) {}
}
}
Listing 9
The class SnapshotEndpoint
receives a string message and converts it using the Java Architecture for XML Binding (JAXB) API. The Snapshot
domain object is an annotated POJO:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Snapshot {
//...arbitrary fields
private long id;
}
Listing 10
The JSR 356 API supports extensions, so the serialization and deserialization could be factored out into a dedicated class. Also, we are not limited to JAXB; we can use any available representation of the object, such as JSON or serialization. The SnapshotEndpoint
is executed on the client side by a dedicated WebSocket thread, so the message cannot be used to update only the UI. With the Platform.runLater
method, the message is properly passed from the WebSocket to the JavaFX thread.
An Endpoint
is responsible only for the actual communication. In addition, the WebSocket container requires initial configuration and initialization for what is implemented in a dedicated class:
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) {
}
}
}
Listing 11
So far, we have barely used JavaFX integration capabilities; instead, we have focused on various integration styles. JavaFX properties, however, make synchronous and asynchronous integration particularly interesting.
Integration the JavaFX Way
A javafx.beans.property.ObjectProperty
wraps an Object
instance and is bindable. An interested client can register as a listener or directly bind to the property and receive notifications if the wrapped instance gets replaced. (See the "Binding for Narrow Interfaces" section of the Java Magazine article "JavaFX Data Binding for Enterprise Applications.") Regardless of the communication protocol and synchronicity, a response carries a domain object that has to be displayed by the UI. With an ObjectProperty
, the UI can just directly bind to the value and get automatically notified upon data arrival. The presenter directly binds to the ObjectProperty
, which makes additional management methods superfluous:
this.snapshotSocketListener.snapshotProperty().
addListener((o, oldValue, newValue) -> {
snapshots.add(newValue);
onSnapshotArrival(newValue);
});
Listing 12
A bindable ObjectProperty
significantly removes the clutter and makes the interface remarkably "narrow." Binding can also be applied for the outbound communication. A presenter changes the state of a domain object or model, which causes the notification of the service (Business Delegate). The outbound communication does not require any synchronization with the UI threads. Asynchronous operations can even be directly executed from the UI thread, and long-running operations can either be wrapped with a javafx.concurrent.Service
or asynchronously executed within the Business Delegate. However, not all UI operations change the state of a domain object. Simple user actions such as a "save" or "refresh" can be directly translated to Business Delegate method invocations.
One Step Further for Responsiveness and Simplicity
A JavaFX UI is event driven and asynchronous. Also, Java EE 7 APIs—such as JAX-RS 2.0, JMS, or WebSockets—come with asynchronous capabilities. Using JavaFX together with asynchronous Java EE 7 APIs significantly simplifies the code. All Business Delegate operations can be performed asynchronously without blocking the UI or even the Business Delegate itself. The interaction pattern is independent of the communication protocol and can be applied consistently for all asynchronous Java EE 7 APIs.
A request is sent to the server in a "fire-and-forget" fashion. Instead of waiting for the response, a callback method is registered for response processing. The callback receives the data, populates a domain object, and replaces the current domain object using the ObjectProperty#set
within the Platform.runLater
method. Changes to the domain object are propagated to the presenter, which multicasts the changes to any interesting views.
A fully asynchronous communication greatly reduces the amount of necessary code. Using a fire-and-forget approach in both directions with data binding eliminates the need for data synchronization between a temporary model and the master state on the server side. All actions are directly passed to the Business Delegate, and all responses coming from the application server directly update the UI.
Also the user experience (UX) can be greatly improved with fully asynchronous interaction; regardless of how expensive a server side interaction is, the UI will never block.
Conclusion
At first glance, the integration of JavaFX with back-end services is very similar to Swing. Platform#runLater
is equivalent to javax.swing.SwingUtilities#invokeLater
and the intentions of javafx.concurrent.Service
are similar to javax.swing.SwingWorker
.
Modern Java EE 7 APIs, JavaFX data binding (see "JavaFX Data Binding for Enterprise Applications"), and the Inversion of Control capabilities with FXML and Scene Builder (see "Integrating JavaFX Scene Builder into Enterprise Applications") allow us to greatly simplify the presentation logic and introduce consistent approaches for implementing multiview desktop applications.
With Java EE 6 and 7 back ends, you can continue with the asynchronous interaction style on the server side as well.
See Also
About the Author
Consultant and author Adam Bien is an Expert Group member for the Java EE 6 and 7, EJB 3.X, JAX-RS, and JPA 2.X JSRs. He has worked with Java technology since JDK 1.0 and with Servlets/EJB 1.0 and is now an architect and developer for Java SE and Java EE projects. He has edited several books about JavaFX, J2EE, and Java EE, and he is the author of Real World Java EE Patterns—Rethinking Best Practices and Real World Java EE Night Hacks—Dissecting the Business Tier. Adam is also a Java Champion, Top Java Ambassador 2012, and JavaOne 2009, 2011, and 2012 Rock Star. Adam occasionally organizes Java EE workshops at Munich's Airport (http://airhacks.com).
Join the Conversation
Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!