Teil 2

Java 8 – CompletableFuture in freier Wildbahn

Sven Ruppert
©shutterstock/svilen_mitkov

Java 8 gibt einem neue Werkzeuge im Bereich Concurrency an die Hand. Auch im zweiten Teil dieser Serie wollen wir uns ansehen, wie man damit z. B. die beiden unterschiedlichen LifeCycles von CDI und JavaFX vereinbaren kann.

Im letzten Teil haben wir uns angesehen, wie man mit einem CompletableFuture Methodenaufrufe in ihrer Reihenfolge voneinander entkoppeln kann. Jetzt werden wir dies anwenden, um den Einsatz von CDI und JavaFX komfortabler zu gestalten.

Die Herausforderung

Basierend auf den vorgegangenen Artikeln zum Thema „CDI bootstraping in JavaFX“ sehen wir uns heute an, wie für den Entwickler die bisherigen Pattern in der Handhabung vereinfacht werden können. Die Herausforderung lag bisher immer darin, dass die initialisierenden Methoden von den jeweiligen Frameworks teilweise in unterschiedlicher Reihenfolge aufgerufen wurden. Das lag immer daran, dass der treibende Prozess entweder CDI oder JavaFX gewesen ist. Das bedeutete auch, dass aus dem einen Initialisierungsprozess der jeweils andere getriggert worden ist. Passiert das immer in derselben Reihenfolge, kommt es nicht zu Problemen da man sich darauf einstellen kann. Aber es ist immer wichtig die Reihenfolge bei der Implementierung zu beachten. Das wird sich jetzt ändern.

Die Initialisierung als Wiederholung

Zur Wiederholung hier der grundlegende Basisprozess: Eine Komponente wird instanziiert und der dazugehörige Controller dann vom FXMLLoader geladen. Dazu wird die ControllerFactory überschrieben. Das hat den Sinn, dass an dieser Stelle der CDI Initialisierungsprozess getriggert werden kann. Natürlich nur für die Instanz des Controllers. Für den Artikel jetzt reicht dieser Ansatz vollständig aus, um die Wirkungsweise zu demonstrieren, denn hier passiert genau das wie vorher beschrieben. Innerhalb des Initialisierungsprozesses (JavaFX) wird auch der von CDI getriggert. Der Controller selbst weiß allerdings nicht, in welcher Reihenfolge hier was von außen passieren wird. Es wird dem Controller lediglich mitgeteilt, dass der Initialisierungsprozess begonnen hat (controller.initInstance()).

@Inject Instance<CDIJavaFxBaseController> instance;
//…
loader.setControllerFactory(
        new Callback<Class<?>, Object>() {
    @Override
    public Object call(Class<?> param) {
        final Class<JavaFXBaseController> p 
            = (Class<JavaFXBaseController>) param;
        final JavaFXBaseController controller 
            = instance.select(p).get();
        controller.initInstance(); //trigger async call
        return controller;
    }
});
try {
    final Class<?> aClass 
        = Class.forName(clazz.getName() + "Controller");
    final CDIJavaFxBaseController call 
        = (CDIJavaFxBaseController) loader.getControllerFactory()
            .call(aClass);
    loader.setController(call);
} catch (ClassNotFoundException e) { logger.error(e); }

Die Implementierung

Im Listing 1 ist die Instanziierung der Controllerinstanz zu sehen. Die Instanz selbst ist dann schon von CDI gemanaged. Auf dieser Instanz wird die Methode initInstance() aufgerufen. Dabei handelt es sich um den Trigger, der die Synchronisation der beiden Initialisierungsprozesse startet. Was also genau passiert nun?

 private Boolean initCompleteCDI = false;
    private Boolean initCompleteFX = false;
@Override
public final void initInstance(){
        final CachedThreadPoolSingleton instance = 
            CachedThreadPoolSingleton.getInstance();
        supplyAsync = CompletableFuture
            .supplyAsync(task, instance.cachedThreadPool);
        if (logger.isDebugEnabled())
            supplyAsync.thenAccept(logger::debug);  //logger
}
public final Supplier<String> task = ()-> {
//      Warten bis alle true
        while(! (initCompleteCDI && initCompleteFX) ){
            try {
                //evtl loggen
                if (logger.isDebugEnabled()) {
                    logger.debug("initCompleteCDI 
                        = " + initCompleteCDI);
                    logger.debug("initCompleteFX 
                        = " + initCompleteFX);
                    logger.debug("ClassName = " 
                                    + getClass().getName());
                }
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return e.toString();
            }
        }
        final boolean fxApplicationThread 
            = Platform.isFxApplicationThread();
        if ( ! fxApplicationThread){
            Platform.runLater(this::initBusinessLogic);
        } else {
            initBusinessLogic();
        }
        return DONE;
};

In Listing 2 wird der Supplier einem ThreadPool zur Ausführung übergeben. Dieser Aufruf ist allerdings nicht blockierend. Erst wenn die beiden Attribute (initCompleteCDI, initCompleteFX) true sind, wird die Methode initBusinessLogic() aufgerufen. Das jeweilige Attribut (CDI oder FX) wird in der dazugehörigen Init-Methode gesetzt (Listing 3). Fertig ist der LifeCycle.

@PostConstruct
public final void postconstruct(){
    cdiPostConstruct();
    initCompleteCDI = true;
    if (logger.isDebugEnabled()) {
        logger.debug("postconstruct ready " 
            + getClass().getName());
    }
}

public abstract void cdiPostConstruct();

@Override
public final void initialize(URL url, 
                                ResourceBundle resourceBundle) {
    initializeFX(url, resourceBundle);
    initCompleteFX = true;
    if (logger.isDebugEnabled()) {
        logger.debug("initialize ready " 
            + getClass().getName());
    }
}

protected abstract void initializeFX(URL url, 
                            ResourceBundle resourceBundle);
/**
* wird nach der init von CDI und JavaFX aufgerufen,
* egal in welcher Reihenfolge die init durchlaufen wird.
*
* ein blockierender method call
*
*/
public abstract void initBusinessLogic();

Die jeweils vom Framework vorgesehenen Methoden werden final deklariert. Innerhalb der Implementierung wird die neu definierte abstrakte Methode aufgerufen, damit der Entwickler die Möglichkeit hat, gezielt dort weitere Initialisierungen vorzunehmen. Das sollte dann aber nur in seltenen Fällen passieren. Zusätzlich wird das korrespondierende Attribut auf true gesetzt, wenn der Methodenaufruf erfolgreich gewesen ist. Für den Entwickler, der dieses Konstrukt verwendet, sollte ausschließlich die Implementierung der Methode initBusinessLogic() von Interesse sein. Hier ist sichergestellt, dass alle Komponenten von CDI und JavaFX vorhanden und initialisiert sind.

Fazit

Mit dem CompletableFuture kann man sehr schön und einfach asynchrone Abläufe erzeugen und an definierten Punkten wieder zusammenlaufen lassen. Die Komplexität im Vergleich zu einer Lösung mittels Threads ist erheblich geringer. Wir haben uns in diesem Teil angesehen, wie dieses verwendet werden kann, um die beiden LifeCycle von CDI und JavaFX robust zu verbinden und dem Entwickler einen einfachen Einstiegspunkt für die Implementierung der Initialisierung der Geschäftslogik zu geben. In diesem Zuge möchte ich auf das kleine Open-Source-Projekt RapidPM aufmerksam machen. Dort sind in den Modulen

cdi-commons,
cdi-commons-se und
cdi-commons-fx 

einige der hier besprochenen Pattern abgelegt.

Die Quelltexte zu diesem Text sind auf der rapidpm-Seite zu finden. Wer umfangreichere Beispiele zu diesem Thema sehen möchte, dem empfehle einen Blick in die Module auf BitBucket.

Aufmacherbild: Online trading concept von Shutterstock / Urheberrecht: svilen_mitkov

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: