JavaFX 8 with Spring integration

In dem folgenden Artikel möchte ich euch eine Möglichkeit zeigen, wie ihr JavaFX 8 und Spring zusammen einsetzen könnt.

Damit wir mit dem Programmieren anfangen können, erstellen wir uns zum Bespiel in Netbeans ein neues Projekt (Maven -> JavaFX Application).

Als Einstiegspunkt für unsere Anwendung erstellen wir uns eine neue Klasse DemoApplication. Es ist zu beachten, dass in der pom.xml die MainClass angepasst wird.

 <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <mainClass>de.codelife.demoproject.main.DemoApplication</mainClass>
 </properties>

Damit wir Spring in unserem Projekt benutzen können, tragen wir die für uns notwendigen Abhängigkeiten in die pom.xml ein.

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.0.6.RELEASE</version>
        </dependency>
</dependencies>

Als vorerst letzten Schritt erstellen wir uns in dem Paket Other Sources -> src/main/resources eine properties-Datei und nennen diese application. Die application.properties hilft uns, diverse Einstellungen für die Anwendung an einem zentralen Punkt zu verwalten. Im späteren Verlauf werden wir auf die hier hinterlegten Informationen zugreifen und in der Application verwenden.

Fügen wir in die Datei ein paar Informationen für unsere Anwendung hinzu.

application.name = DemoApplication
application.version = 1.0
fxml.demo.view=/fxml/Demo.fxml

DemoApplication

Da wir wollen, dass unser ApplicationContext beim Start der Anwendung direkt mit hochgefahren wird, erzeugen wir diesen in der start() Methode unserer DemoApplication Klasse. In diesem einfachen Beispiel dient diese Klasse auch als Konfiguration.

@Configuration
@ComponentScan("de.codelife.demoproject")
@PropertySource("classpath:application.properties")
public class DemoApplication
        extends Application
{

    private static final Logger LOG = Logger.getLogger(DemoApplication.class.getName());

    private ApplicationContext appContext;

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

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        appContext = new AnnotationConfigApplicationContext(DemoApplication.class);
        String name = appContext.getEnvironment().getProperty("application.name");
        String version = appContext.getEnvironment().getProperty("application.version");
        LOG.log(Level.INFO, String.format("%s %s wurde gestartet", name, version));
    }   
}

Wenn wir die Anwendung jetzt starten, erhalten wir in der Konsole folgende Ausgabe: „INFORMATION: DemoApplication 1.0 wurde gestartet“.

Bringen wir JavaFX ins Spiel (FXML)

Zunächst erstellen wir ein neues FXML-File und nennen es Demo.fxml. Mit Hilfe des SceneBuilders fügen wir der Oberfläche ein paar UI-Elemente hinzu, so dass die Datei danach wie folgt aussieht.

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>


<AnchorPane id="AnchorPane" prefHeight="100.0" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
  <children>
 <Button fx:id="btn" layoutX="14.0" layoutY="65.0" mnemonicParsing="false" onAction="#showOutput" text="click counter" />
 <Label fx:id="output" layoutX="14.0" layoutY="42.0" text="Label" />
 <TextField fx:id="input" layoutX="14.0" layoutY="14.0" prefHeight="25.0" prefWidth="277.0" />
 <Label fx:id="btnMessage" layoutX="105.0" layoutY="69.0" text="Label" />
 </children>
</AnchorPane>

 FXML und Controller

Nur durch das Erstellen dieser Datei erhalten wir noch keine Anzeige. Zu jeder FXML gehört auch ein Controller, der mit den Elementen in der Oberfläche interagiert.

Die Verknüpfung zwischen der FXML-Datei und dem Controller überlassen wir einer abstrakten Oberklasse.

public abstract class FXMLController
        implements InitializingBean, Initializable {

    public FXMLController() {
        super();
    }

    protected Node view;

    protected String fxmlFilePath;

    public abstract void setFxmlFilePath(String filePath);

//    Wenn diese Methode bereitgestellt wird, kann auf das Interface "Initializable" verzichtet werden.
//    public abstract void initialize();
    
    @Override
    public void afterPropertiesSet() throws Exception {
        loadFXML();
    }

    protected final void loadFXML() throws IOException {
        try (InputStream fxmlStream = getClass().getResourceAsStream(fxmlFilePath)) {
            FXMLLoader loader = new FXMLLoader();
            loader.setController(this);
            this.view = (loader.load(fxmlStream));
        }
    }

    public Node getView() {
        return view;
    }
}

 

Die Klasse FXMLController hat einige Besonderheiten auf die ich im weiteren Artikel eingehen möchte.

public abstract void setFxmlFilePath(String filePath)

Diesen Setter müssen die Unterklassen implementieren, damit wir uns Einträge aus der application.properties durch Spring injecten lassen können. In unserem DemoController schreiben wir lediglich die Annotation @Value() über den Setter unserer Methode setFxmlFilePath. Dort geben wir den exakten Namen der Property fxml.demo.view aus der properties-Datei an. Somit steht in unserer Variablen fxmlFilePath der Pfad zu dem FXML-File /fxml/Demo.fxml.

public void afterPropertiesSet()

Nachdem die Controllerklasse von Spring erzeugt wurde, können wir auch mit dem Laden der Oberfläche anfangen. Dies machen wir am einfachsten in der Methode afterPropertiesSet(). Die Methode wird automatisch von Spring aufgerufen, wenn wir das Interface InitializingBean einbinden. Sollten wir an einer früheren Stelle mit dem Laden der FXML beginnen und auf die Stringvariable fxmlFilePath zugreifen, würden wir einen NULL-Pointer erhalten.

protected final void loadFXML()

In dieser Methode wird die Verbindung zwischen Controller und Oberfläche hergestellt. Genauer gesagt macht dies der FXMLLoader für uns. Wir setzen unseren Controller in den Loader, lassen ihn unsere FXML-Datei in Form eines InputStreams laden und speichern dies in einer Variable view. In diesem Objekt befinden sich nun alle Elemente die sich auf dieser FXML-Oberfläche befinden.

Initializable vs initialize()

FXMLLoader ruft automatisch eine geeignete initialize() -Methode auf dem definierten Controller auf. Dadurch ist es nicht notwendig, das Interface Intitializable einzubinden. Möchte man den Nutzer der abstrakten Klasse dazu zwingen, eine entsprechende Methode zu implementieren, kann dies durch das Einbinden des Interfaces Initializable oder durch das Erstellen einer abstrakten Methode mit dem Namen initialize() erreicht werden.

DemoController

@Component
public class DemoController
        extends FXMLController {

    public DemoController() {
        super();
    }

    private static final AtomicLong counter = new AtomicLong(1);

    @FXML
    Button btn;

    @FXML
    TextField input;

    @FXML
    Label btnMessage;

    @FXML
    Label output;

    @FXML
    public void showOutput() {
        btnMessage.setText(String.format("Counter: %d", counter.getAndIncrement()));
    }

    @Value("${fxml.demo.view}")
    @Override
    public void setFxmlFilePath(String filePath) {
        this.fxmlFilePath = filePath;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        output.textProperty().bind(input.textProperty());
    }
}

 

Die Variablen und Methoden, die mit der Annotation @FXML versehen sind, müssen exakt so geschrieben sein, wie in der FXML-Datei. Auch wenn Spring bereits die afterPropertiesSet Methode aufgerufen hat, sind diese Variablen weiterhin null. Nur wenn der Name übereinstimmt und der FXMLLoader die FXML-Datei geladen hat, können die Elemente von JavaFX injected werden.

Deswegen bietet sich eine initialize()-Methode an, um zum Beispiel ein Binding zwischen zwei UI-Elementen herzustellen.

 

Welche Methode wird wann und von wem aufgerufen?

1. DemoController Konstruktor

2. FXMLController Konstruktor

3. public void setFxmlFilePath(String filePath) – Spring

4. public void afterPropertiesSet() – Spring

5. protected final void loadFXML()

6. public void initialize(URL location, ResourceBundle resources) – JavaFX

Letzte Anpassungen

Zu guter Letzt müssen wir in unserer Startklasse DemoApplication folgenden Code ergänzen.

@Override
    public void start(Stage primaryStage) throws Exception {
        appContext = new AnnotationConfigApplicationContext(DemoApplication.class);
        String name = appContext.getEnvironment().getProperty("application.name");
        String version = appContext.getEnvironment().getProperty("application.version");

        DemoController demoController = appContext.getBean(DemoController.class);

        primaryStage.setScene(new Scene((Parent) demoController.getView()));
        primaryStage.setTitle(String.format("%s %s", name, version));
        primaryStage.show();

        LOG.log(Level.INFO, String.format("%s %s wurde gestartet", name, version));
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

 

Ergebnis

DemoApplication

Den Quellcode findet ihr hier.

 

JavaFX 8 with Spring integration
Markiert in:                 

2 Gedanken zu „JavaFX 8 with Spring integration

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.