JavaFx + Spring Boot + Gradle: Trzech bohaterów na jednej scenie

JavaFx + Spring Boot + Gradle: Trzech bohaterów na jednej scenie

Dziś mam przyjemność przedstawić Ci trzech bohaterów, których zapewne znasz, a przynajmniej o nich słyszałeś. Każdy z nich jest, lub chociaż pretenduje do tytułu mistrza w swojej klasie. Mowa oczywiście o tytułowych JavaFX, Spring Boot oraz spinającym cały projekt – Gradle. Zobacz jak w praktyce wygląda skonfigurowanie projektu od zera, który pozwala wykorzystać zalety całej trójki.

Agenda
  1. Czego dowiesz się z tego poradnika
  2. Tworzenie projektu Gradle/Java w IntelliJ IDEA
  3. Konfiguracja aplikacji Spring Boot w środowisku Gradle
  4. Integracja aplikacji JavaFX ze Spring Boot
  5. Chwila prawdy – czyli test opisanego rozwiązania

 

Źródła aplikacji

Aktualną wersję szablonu opisanego w tym poradniku możesz sklonować z mojego repozytorium:

git clone https://gitlab.com/better-coding.com/templates/javafx-spring-boot-gradle.git

 

1. Czego dowiesz się z tego poradnika

Całkiem niedawno stanąłem przed projektem napisania aplikacji Java przeznaczonej na desktopy. Dość naturalnym wyborem było użycie JavaFX jako głównego frameworka do GUI, tym bardziej, że wyparł on takie legendy jak SWING czy AWT. Obecnie JavaFX jest również rekomendowany przez Oracle jako podstawowy framework Java do programowania GUI na desktopach.

Przystępując do planowania oraz implementacji kolejnych wymagań, dość szybko zaczęło mi brakować tego, co znałem ze świata Spring MVC. Mianowicie, chodzi przede wszystkim o IOC oraz potężnego oręża narzędzi i rozwiązań jakimi obecnie włada Spring. Mam tu na myśli chociażby mechanizmy konfiguracji, profili, logowania, servletów oraz doskonałej integracji z całą masą standardów. Gdy tylko ten pomysł narodził się w mojej głowie,  już wiedziałem, że chcę Spring’a  w swoim projekcie. Tak też zrobiłem a teraz to opisuję.

Ostatnim z trójki jest Gradle – wisienka na torcie. Jest to potężne narzędzie do budowania projektów. Dla niewtajemniczonych – to taki Maven, tylko na sterydach. Gradle jest niesamowicie elastyczny z ekstremalnie prostą możliwością dostosowania do dowolnego projektu (praktycznie w każdym języku, nie tylko w Java).

W tym poradniku opisuję cały proces konfiguracji tytułowego projektu rozpoczynając od zgromadzenia programów i narzędzi, poprzez integrację Spring Boot z JavaFX, aż do testów finalnej aplikacji.

 

2. Tworzenie projektu Gradle/Java w IntelliJ IDEA

Na potrzeby tego wpisu jako IDE użyję IntelliJ IDEA oraz JDK8. Przystępując do tworzenia projektu warto przygotować sobie narzędzia i biblioteki, do których linki zamieszczam poniżej:

Gdy już zgromadzimy narzędzia możemy przystąpić do tworzenia samego projektu. Pierwszy wybór przed którym staniemy, to narzędzie do budowania projektu i zarządzania zależnościami bibliotek. Maven czy Gradle – na potrzeby tego poradnika wybrałem drugą opcję. Przystąpmy zatem do tworzenia projektu.

Na ekranie nowego projektu wybieramy Gradle oraz zaznaczamy Java w panelu dodatkowych bibliotek. Pamiętaj również o wybraniu odpowiedniego JDK.

 

Następnie podajemy metadane znane Ci już zapewne z projektów Maven – czyli grupę nazwę i wersję opisujące naszą aplikację.

 

Na koniec konfigurujemy Gradle. Najważniejsze to zaznaczenie opcji Use auto-import oraz  Use default gradle wrapper. Upewnij się również, że Gradle JVM wskazuje na aktualne JDK.

 

To byłoby na tyle jeśli chodzi o konfigurację projektu Gradle – możemy zatem przejść do kolejnego kroku.

 

3. Konfiguracja aplikacji Spring Boot w środowisku Gradle

Spring to niekwestionowany mistrz w kategorii frameworków IOC, dlatego też zdecydowałem się wykorzystać jego zalety oraz niesamowite możliwości w swoim projekcie. Integrację Spring Boot z Gradle opisałem dokładnie w poniższym wpisie.

 

Bazując na przedstawionym wpisie należy zmodyfikować plik build.gradle do postaci zamieszczonej poniżej oraz utworzyć klasę MyApp.java w celu przetestowania, czy wszystko poszło jak należy.

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.4.RELEASE"
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group 'com.bettercoding.jfx'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

 

package com.bettercoding.jfx;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                System.out.println("Hello World");
            }
        };
    }
}

 

Po uruchomieniu aplikacji powinniśmy zobaczyć poniższy rezultat, czyli komunikat Hello World oraz zakończenie aplikacji z kodem 0.

 

4. Integracja aplikacji JavaFX ze Spring Boot


Zacznijmy od stworzenia formatki sample.fxml w folderze resources/fxml składającej się z panelu z umieszczonym wewnątrz komponentem WebView. Wykorzystamy go w dalszej części,  w celu sprawdzenia integracji ze Spring Boot.

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


<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.web.WebView?>
<BorderPane maxHeight="-Infinity"
            maxWidth="-Infinity"
            minHeight="-Infinity"
            minWidth="-Infinity"
            prefHeight="800.0"
            prefWidth="600.0"
            xmlns="http://javafx.com/javafx/8.0.121"
            xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="com.bettercoding.jfx.MyApp">
   <center>
       <WebView fx:id="myWebView" />
   </center>
</BorderPane>

 

Kolejnym krokiem zbliżającym nas do celu jest zmodyfikowanie klasy MyApp.java, która pierwotnie była tylko aplikacją Spring Boot, do postaci aplikacji JavaFX będącej pod kontrolą Spring’a. W tym celu zmodyfikuj ją tak, aby dziedziczyła po klasie javafx.application.Application.

package com.bettercoding.jfx;

import javafx.application.Application;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApp extends Application {

 

Następnie przygotuj kilka pól pomocniczych, które wykorzystamy w metodach implementowanych w dalszych krokach.

@SpringBootApplication
public class MyApp extends Application {
    private ConfigurableApplicationContext springContext;
    private Parent rootNode;
    private FXMLLoader fxmlLoader;

 

Zmianie ulega przede wszystkim sposób uruchomienia aplikacji. Obecnie w metodzie main mamy kod startujący kontekst Springa. Należy zmienić go na metodę launch startującą aplikację JavaFx, a sam start Springa zrobimy w kolejnym kroku.

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

 

Płynnie przechodzimy do implementacji najważniejszej metody z punktu widzenia integracji JavaFX ze Spring Boot, a mianowicie init. Uruchamiamy w niej kontekst Spring’a, tworzymy FXMLLoader oraz przekazujemy kontrolę nad tworzonymi bean’ami do fabryki bean’ów Spring’a.

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(MyApp.class);
        fxmlLoader = new FXMLLoader();
        fxmlLoader.setControllerFactory(springContext::getBean);
    }

 

Metoda start jest w zasadzie standardowym kodem spotykanym w czystej aplikacji JavaFX. Zawiera wczytanie formatki oraz przygotowanie i wyświetlenie sceny.

@Override
    public void start(Stage primaryStage) throws Exception{
        fxmlLoader.setLocation(getClass().getResource("/fxml/sample.fxml"));
        rootNode = fxmlLoader.load();

        primaryStage.setTitle("Hello World");
        Scene scene = new Scene(rootNode, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

 

Ostatnim elementem z punktu widzenia integracji JavaFx ze Spring Boot jest zatrzymanie Springa podczas zatrzymywania aplikacji JavaFx. Efekt ten osiagamy nadpisując metodę stop poniższeym kodem.

@Override
public void stop() {
    springContext.stop();
}

 

Dla czytelności postanowiłem usunąć  CommandLineRunner. Poniżej znajduje się finalna wersja kodu aplikacji będąca już w pełni funkcjonalną hybrydą JavaFx oraz Spring Boot.

package com.bettercoding.jfx;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class MyApp extends Application {
    private ConfigurableApplicationContext springContext;
    private Parent rootNode;
    private FXMLLoader fxmlLoader;

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

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(MyApp.class);
        fxmlLoader = new FXMLLoader();
        fxmlLoader.setControllerFactory(springContext::getBean);
    }

    @Override
    public void start(Stage primaryStage) throws Exception{
        fxmlLoader.setLocation(getClass().getResource("/fxml/sample.fxml"));
        rootNode = fxmlLoader.load();
        
        primaryStage.setTitle("Hello World");
        Scene scene = new Scene(rootNode, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() {
        springContext.stop();
    }
}

 

5. Chwila prawdy – czyli test opisanego rozwiązania


Najbardziej klasycznym przypadkiem, który również świetnie nadaje się na test opisanej integracji, jest moim zdaniem przygotowanie bean’a kontrolera formatki sample.fxml, oraz jego dostarczenie za pomocą fabryki bean’ów Spring’a. Zadaniem wspomnianego kontrolera będzie załadowanie strony mojego bloga w komponencie WebView. Adres strony będzie dostarczony z pliku application.properties.

Zacznijmy zatem od przygotowania pliku application.properties o następującej treści

my.url=https://better-coding.com

 

Następnie tworzymy kontroler oraz wpinamy go w sample.fxml.

package com.bettercoding.jfx;

import javafx.fxml.FXML;
import javafx.scene.web.WebView;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;

@Controller
public class SampleController {
    @FXML
    private WebView myWebView;

    @Value("${my.url}")
    private String myUrl;

    @FXML
    private void initialize() {
        myWebView.getEngine().load(myUrl);
    }
}

 

Warto zwrócić uwagę na linię 13 kontrolera, gdzie wstrzykujemy wartość property w pole kontrolera, oraz linie 16-19, w których wykorzystujemy je w celu załadowania strony internetowej w komponencie WebView.

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


<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.web.WebView?>
<BorderPane maxHeight="-Infinity"
            maxWidth="-Infinity"
            minHeight="-Infinity"
            minWidth="-Infinity"
            prefHeight="800.0"
            prefWidth="600.0"
            xmlns="http://javafx.com/javafx/8.0.121"
            xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="com.bettercoding.jfx.SampleController">
   <center>
       <WebView fx:id="myWebView" />
   </center>
</BorderPane>

 

Uwaga!
Kontroler musi być koniecznie oznaczony adnotacją @Controller tak aby Spring mógł go wykryć i powołać. W przeciwnym wypadku dostaniemy wyjątek:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.bettercoding.jfx.SampleController' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:346)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:333)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
	at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:929)
	at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
	at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
	at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
	at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
	at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)

 

W tym momencie nasza aplikacja nadaje się na ostateczny test. Jeśli postępowałeś według tego poradnika, to struktura projektu powinna być analogiczna do poniższej.

 

Nie pozostaje nam już nic innego jak uruchomić naszą aplikację i trzymać kciuki aby otrzymać poniższy rezultat.

 

To byłoby na tyle jeśli chodzi o ten wpis. Jeśli podoba Ci się konwencja i chciałbyś więcej takich wpisów, zostaw mi komentarz z tematem, który Cię interesuje, a ja postaram się go przygotować. Tymczasem możesz mi pomóc dotrzeć do większej liczby odbiorców udostępniając ten wpis.
Z góry dziękuję.

4
Leave a Reply

avatar
3 Comment threads
1 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
lukasz.cieslaPiotrek Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
trackback

[…] JavaFx + Spring Boot + Gradle: Trzech bohaterów na jednej scenie […]

trackback

[…] JavaFx + Spring Boot + Gradle: Trzech bohaterów na jednej scenie […]

Piotrek
Guest
Piotrek

Bardzo fajnie napisany artykuł, jedyny chyba jaki znalazłem po polsku. Bardzo mało jest poradników używających tych technologii razem, co jest bardzo smutne. Ale tak trzymaj!

Close Menu