How to pimp your JavaFX Controller

Sven Ruppert

© shutterstock.com/svilen_mitkov

Wie kann ich einfach die Geschäftsprozess-Implementierung von einem JavaFX Controller trennen? Wir kann ich das auch noch dynamisch halten, bei wenig Aufwand als wartbarer Code und für den Entwickler immer noch als Clean Code durchgehen lassen? Vielleicht habe ich hier einen Ansatz.

Basierend auf dem Artikel CDI managed DynamicObjectAdapter werden wir jetzt den JavaFX Controller so erweitern, dass wir die Geschäftsprozessimplementierung dynamisch zur Laufzeit transparent verändern können. Aber bitte ohne Boilerplate-Code!

Wie alles beginnt

Beginnen wir mit einem kleinen Projekt, in dem das Beispiel aus dem vorweg genannten Artikel verwendet werden soll. Zur Erinnerung, es gab ein Interface DemoLogic in dem zwei Methoden deklariert wurden. Die Methoden haben die Namen add(..) und sub(..). Wie die Namen vermuten lassen, handelt es sich um eine triviale Addition und Subtraktion. Die Besonderheit wird dann sichtbar, wenn man zur Laufzeit die Implementierung umschaltet, die für die jeweiligen Methoden angeboten wird. Wir haben damit einen dynamischen Decorator mittels CDI, ohne die Einschränkungen, die uns die Implementierung von CDI selbst liefert.

Nun zu der Integration in JavaFX. Für das GUI erzeugen wir erst einmal eine Datei mit dem Namen DemoPane.fxml (Listing 1). Hier werden die notwendigen GUI-Elemente definiert. Wir verwenden einen Button um die Aktion auszulösen. Außerdem zwei Textfelder, die wir für die Eingaben benutzen – hier sind Integer einzugeben. Um das Beispiel so klein wie möglich zu halten, wurde auf Fehlerhandling komplett verzichtet. Umschalten der Logic bzw. des Kontextes erfolgt durch Aktivieren/Deaktivieren der Checkbox. Das Ergebnis der Berechnung ist dann in dem ganz unten angebrachten Label-Element zu sehen.

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.CheckBox?>
<fx:root type="javafx.scene.layout.AnchorPane"
            xmlns:fx="http://javafx.com/fxml">
    <children>
        <VBox>
            <children>
                <Button fx:id="button" text="Hello World" />
                <CheckBox fx:id="checkbox" text="switch context"/>
                <TextField fx:id="textFieldA"/>
                <TextField fx:id="textFieldB"/>
                <Label fx:id="label"/>
            </children>
        </VBox>

    </children>

</fx:root>

Passend zu dem FXML-File gibt es den Controller mit dem Namen DemoController. Hier werden die GUI-Elemente deklariert und mittels Annotation (@FXML) injiziert. In der Methode initialize(..) werden wie gehabt die Komponenten initialisiert und die Verbindung zwischen GUI und DemoLogic hergestellt. Damit macht der DemoController ausschließlich das was er soll, sämtliche Geschäftslogik ist in der Klasse DemoLogic zu finden. Es ist eine Besonderheit im Controller zu finden: die Geschäftslogik und der Kontext werden mittels CDI injiziert (Listing 2).

@Inject
    @DynamicDecoratorTest
    Instance<DemoLogic> demoLogicInstance;

    @Inject
    Context context;

Es wird hier davon ausgegangen, dass der Controller selbst von CDI verwaltet wird. Wie das genau funktioniert, werden wir uns in späteren Artikeln im Detail ansehen. Wir werden es hier im JUnit-Test bootstrapen. Wer allerdings nicht warten möchte und wissen will wie das im Detail in JavaFX-Anwendungen realisiert werden kann, dem kann ich den Artikel CDI JavaFX Bootstrapping in der aktuellen Ausgabe vom Java Magazin empfehlen.

@DynamicDecoratorTest
public class DemoController implements Initializable{

    @FXML public TextField textFieldA;
    @FXML public TextField textFieldB;
    @FXML public Button button;
    @FXML public Label label;
    @FXML public CheckBox checkbox;

    @Inject
    @DynamicDecoratorTest
    Instance<DemoLogic> demoLogicInstance;

    @Inject
    Context context;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        button.setText("klick me");
        button.setOnAction(actionEvent -> {

            final DemoLogic demoLogic = demoLogicInstance.get();

            final String textFieldAText = textFieldA.getText();
            final Integer a = Integer.valueOf(textFieldAText);

            final String textFieldBText = textFieldB.getText();
            final Integer b = Integer.valueOf(textFieldBText);

            final int result = demoLogic.add(a, b);
            label.setText(result+"");

        });

        checkbox.setOnAction(actionEvent -> {
            context.original = checkbox.isSelected();
        });
    }
}

Aus der Sicht eines Entwicklers

Der Entwickler selbst sieht an der Stelle nicht mehr, dass es sich bei der DemoLogic um eine recht dynamische Komponente handelt. Es geht hier ausschließlich um die Abbildung der notwendigen Geschäftsprozesse. Der JavaFX Controller ist wieder das, was er aus meiner Sicht auch sein sollte. In der Klasse DemoLogicTest ist die lauffähige JUnit-Version mittels Arquillian zu finden. Ich verwende hier Arquillian, um den CDI Bootstrap zu simulieren. In der Test-Klasse befindet sich die Klasse DemoApp, bei der es sich um die JavaFX-Anwendung handelt. Hier starte ich die Anwendung gezielt nach der Initialisierung mittels CDI, um die beiden Lifecycle zu trennen. Der Aufbau der JavaFX-Anwendung hat eine Besonderheit. Der FXMLoader ist hier außerhalb der Klasse DemoApp, aber innerhalb des Scopes der Test-Klasse definiert. Das ist die einfachste Art eine mit final deklarierte Variable zu übergeben. Zur Erinnerung: das Injizieren mittels CDI kann nicht auf static definierte Variablen erfolgen. In Listing 4 sehen Sie die vollständige Test-Klasse.

@RunWith(Arquillian.class)
public class DemoLogicTest {
    @Deployment
    public static JavaArchive createDeployment() {
        return ShrinkWrap.create(JavaArchive.class)
                .addPackages(true, "org.rapidpm.demo")
                .addPackages(true, "junit.org.rapidpm.demo")
                .addPackages(true, "demo")
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
    }

    @Inject @DynamicDecoratorTest
    Instance<DemoController> demoControllerInstance;
    @Inject
    Context context;

    static final FXMLLoader loader = new FXMLLoader();

    @Test
    public void testDemoLogicJavaFXTest() throws Exception {
        loader.setControllerFactory(param -> demoControllerInstance.get());
        Application.launch(DemoApp.class);
    }


    public static class DemoApp extends Application {
        @Override
        public void start(Stage stage) throws Exception {

            final URL resource = getClass()
                    .getClassLoader()
                    .getResource("DemoPane.fxml");
            loader.setLocation(resource);
            final DemoController controller = (DemoController) loader
                    .getControllerFactory()
                    .call(DemoController.class);
            try {

                loader.setController(controller);
                loader.setRoot(new AnchorPane());
                final Parent root = (Parent) loader.load();

                stage.setScene(new Scene(root));
                stage.setTitle("Custom Control");
                stage.setWidth(300);
                stage.setHeight(200);
                stage.show();
            } catch (IOException exception) {
                throw new RuntimeException(exception);
            }

        }

        public static void main(String[] args) {
            launch(args);
        }

Fazit

Mit diesem Pattern sind wir in der Lage, die Unzulänglichkeiten des CDI Decorators nicht nur auszugleichen, sondern auch die Trennung zwischen GUI-Logik und Geschäftsprozess weiter zu formalisieren.

Die Quelltexte zu diesem Artikel sind unter [1] zu finden. Wer umfangreichere Beispiele zu diesem Thema sehen möchte, dem empfehle einen Blick auf [2].

 

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: