📜 ⬆️ ⬇️

And Spring will come to JavaFX too

Good time of day, habrovchane!

I hope among you there are the same lovers to make molds like me.
The fact is that I have always been a supporter of user-friendly interfaces. I was frustrated by applications that are not very user-oriented, this is especially true in corporate development. And often client applications written in Java are black windows, and GUI applications are treated with skepticism.

Previously, on Swing or AWT, everything was very sad, and probably before the introduction of JavaFX 8, writing anonymous classes turned into spaggeti code. But with the advent of lambda expressions, everything has changed, the code has become simpler, clearer, more beautiful. Using JavaFX in your projects was one pleasure.
')
So, I got the idea to link the best tool for the Java Spring Framework and a handy tool for creating the JavaFX GUI nowadays, this will allow us to use all the features of Spring in the client desktop application. Having collected all the information I had searched for in the open spaces of the network, I decided to share it. First of all I want to note that the article is intended more for beginners, so some of the details for many may be too banal and simple, but I do not want to omit them in order not to lose the integrity of the article.



I look forward to constructive criticism of their decisions.

Who cares, I ask under the cat.

Let's try to write a small application. Suppose there is such a primitive task: you need to write an application that will load product data from the database into a table on the form, and when you click on each row of the table, open an additional window with more detailed data about the product. To fill the database we will use the service. I generated fake data for the table with products and successfully filled the database with them.

It turns out the following.

The main form consists of the following components:

1. Button with the text "Download"
2. TableView with the fields “ID”, “Name”, “Quantity”, “Price”

Functional

  1. When the application starts in context, a bean DataSource will be created and a connection to the database will occur. The connection data is in the configuration file. It is necessary to display 4 fields from the Products table.
  2. When you click on the "Download" button, the TableView will be filled with data from the table.
  3. Double-clicking on a table row will open an additional window with all the Products fields.

Used stack:

Javafx 8
Spring jdbc
SQLite 3
IntelliJ IDEA Community Edition 2017

Create a JavaFX project


Create a new project in IDEA using the Maven archetype. The original structure that we see is quite standard for the maven project:

SpringFXExample ├──.idea ├──src │ ├──main │ │ ├──java │ │ └──resources │ └──test ├──────pom.xml └──────SpringFXExample.iml External Libraries 

We set the required Language Level for the module and the project and change the Target bytecode version for our module in the settings Build, Execution, Deployment -> Compiler -> Java Compiler. Depending on the version of your JDK.

Now you need to turn what came out into a JavaFX application. I give the structure of the project that I want to get below, it does not claim to be ideal.

 SpringFXExample ├──.idea ├──src │ ├──main │ │ ├──java │ │ │ └──org.name │ │ │ ├──app │ │ │ │ ├──controller │ │ │ │ │ ├──MainController.java │ │ │ │ │ └──ProductTableController.java │ │ │ │ └──Launcher.java │ │ │ └──model │ │ │ ├──dao │ │ │ │ └─ProductDao.java │ │ │ └──Product.java │ │ └──resources │ │ └──view │ │ ├──fxml │ │ │ ├──main.fxml │ │ │ └──productTable.fxml │ │ ├──style │ │ └──image │ └──test ├──────pom.xml └──────SpringFXExample.iml External Libraries 

Create the org.name package (or just use the same value as in groupId) in the java directory. The application entry point, controllers, custom elements and utilities for the interface will be located in the app package. Everything else directly concerns the entities used in the application in the model package. In resources, I create a view directory and store * .fxml in the fxml, * .css folder in the style folder and an image in the image folder.

In the main FXML template, we set the application appearance template. It will include the productTable template, which defines the appearance of the table. MainController is our main controller and so far it will be with one method of handling pressing the download button. ProductTableController controller for the table. Launcher is extensible from Application and loaded in the start method our main.fxml in the usual way. Class ProductDao leave for later. But we will write Product on the concept of JavaBean.

Go to the contents of the files:

main.fxml
 <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.control.Button?> <AnchorPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="org.name.app.controller.MainController" prefHeight="400.0" prefWidth="400.0"> <Button fx:id="load" text="" AnchorPane.topAnchor="10" AnchorPane.leftAnchor="10" onMouseClicked="#onClickLoad"/> <!-- TableView     fxml  --> <fx:include AnchorPane.topAnchor="40" AnchorPane.leftAnchor="10" AnchorPane.bottomAnchor="10" source="productTable.fxml"/> </AnchorPane> 


productTable.fxml
 <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.TableColumn?> <?import javafx.scene.control.TableView?> <TableView fx:id="productTable" prefWidth="350.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.name.app.controller.ProductTableController"> <columns> <TableColumn fx:id="id" prefWidth="30.0" text="ID"/> <TableColumn fx:id="name" prefWidth="200.0" text=""/> <TableColumn fx:id="quantity" prefWidth="50.0" text="-"/> <TableColumn fx:id="price" prefWidth="50.0" text=""/> </columns> </TableView> 


MainController.java
 package org.name.app.controller; import javafx.fxml.FXML; import javafx.scene.control.Button; public class MainController { @FXML private Button load; /** *      */ @FXML public void onClickLoad() { System.out.println("..."); // TODO:        DAO  // TODO:         } } 


ProductTableController.java
 package org.name.app.controller; import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import org.name.model.Product; import java.util.List; public class ProductTableController { @FXML private TableColumn<Integer, Product> id; @FXML private TableColumn<String, Product> name; @FXML private TableColumn<Integer, Product> quantity; @FXML private TableColumn<String, Product> price; @FXML private TableView<Product> productTable; /** *  value factory    */ public void initialize() { id.setCellValueFactory(new PropertyValueFactory<>("id")); name.setCellValueFactory(new PropertyValueFactory<>("name")); quantity.setCellValueFactory(new PropertyValueFactory<>("quantity")); price.setCellValueFactory(new PropertyValueFactory<>("price")); } /** *      * @param products   */ public void fillTable(List<Product> products) { productTable.setItems(FXCollections.observableArrayList(products)); } } 


Launcher.java
 package org.name.app; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class Launcher extends Application { public static void main(String[] args) { launch(args); } public void start(Stage stage) throws Exception { Parent root = FXMLLoader.load(getClass() .getResource("/view/fxml/main.fxml")); stage.setTitle("JavaFX Maven Spring"); stage.setScene(new Scene(root)); stage.show(); } } 


Product.java
 package org.name.model; public class Product { private int id; private String name; private int quantity; private String price; private String guid; private int tax; public Product() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } public String getPrice() { return price; } public void setPrice(String price) { this.price = price; } public String getGuid() { return guid; } public void setGuid(String guid) { this.guid = guid; } public int getTax() { return tax; } public void setTax(int tax) { this.tax = tax; } } 


Run to make sure everything works.



First build


We try to build a JAR using the maven package . Having added the following configuration to our pom.xml (I have Java 9 in the project, but this does not mean that I use all of its features, I just select the latest tools for new projects):

 <properties> <maven.compiler.source>9</maven.compiler.source> <maven.compiler.target>9</maven.compiler.target> </properties> 

and maven-jar-plugin:
 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>org.name.app.Launcher</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> 

pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.name</groupId> <artifactId>SpringFXExample</artifactId> <version>1.0</version> <properties> <maven.compiler.source>9</maven.compiler.source> <maven.compiler.target>9</maven.compiler.target> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>org.name.app.Launcher</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project> 


We try to run the resulting jar-nick, if you have properly configured environment variables:
 start java -jar target\SpringFXExample-1.0.jar 

Or using run.bat with the following content:

 set JAVA_HOME=PATH_TO_JDK\bin set JAVA_CMD=%JAVA_HOME%\java start %JAVA_CMD% -jar target\SpringFXExample-1.0.jar 

Personally, I use different JDKs on my PC, so I run applications in this way.



By the way, to hide the terminal, we call not java , but javaw just for the current case, we needed to check the text output when the button was pressed.

Add Spring


Now it's time for Spring, namely, create application-context.xml in resources and write a slightly modified scene loader. Immediately, I note that the idea of ​​the Spring loader for JavaFX is not mine, I have already seen this on the web. But I rethought it a little.

Edit to start our pom.xml . Add Spring Version

 <properties> <maven.compiler.source>9</maven.compiler.source> <maven.compiler.target>9</maven.compiler.target> <spring.version>5.0.3.RELEASE</spring.version> </properties> 

and the spring-context, spring-jdbc, and sqlite-jdbc dependencies.

 <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.7.2</version> </dependency> </dependencies> 

pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.name</groupId> <artifactId>SpringFXExample</artifactId> <version>1.0</version> <properties> <maven.compiler.source>9</maven.compiler.source> <maven.compiler.target>9</maven.compiler.target> <spring.version>5.0.3.RELEASE</spring.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>org.name.app.Launcher</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.7.2</version> </dependency> </dependencies> </project> 


Create a config.properties configuration file . It contains the following data:
# Headline main scene
title = JavaFX & Spring Boot!
# DB Connection Configuration
db.url = jdbc: sqlite: PATH_TO_DB / test_db
db.user = user
db.password = password
db.driver = org.sqlite.JDBC

Add application-context.xml to resources with the following content, if you are at least familiar with the spring, then I think you will not have problems understanding what is written below.

application-context.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="file:config.properties" ignore-unresolvable="true"/> <context:component-scan base-package="org.name"/> <bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${db.url}"/> <property name="driverClassName" value="${db.driver}"/> <property name="username" value="${db.user}"/> <property name="password" value="${db.password}"/> </bean> </beans> 


We write an abstract Controller controller that extends the ApplicationContextAware interface so that we can get the context from any controller.

Controller.java
 package org.name.app.controller; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public abstract class Controller implements ApplicationContextAware { private ApplicationContext context; public ApplicationContext getContext() { return context; } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } } 


Now we are implementing the scene loader SpringStageLoader . It will be more like a utility class in which you can implement the loading of various scenes and windows, so I immediately got it so voluminous.

SpringStageLoader.java
 package org.name.app; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class SpringStageLoader implements ApplicationContextAware { private static ApplicationContext staticContext; //    @Value("${title}") private String appTitle; private static String staticTitle; private static final String FXML_DIR = "/view/fxml/"; private static final String MAIN_STAGE = "main"; /** *         fxml  * @param fxmlName  *.fxml    * @return   Parent * @throws IOException   - */ private static Parent load(String fxmlName) throws IOException { FXMLLoader loader = new FXMLLoader(); // setLocation      ,   productTable.fxml, //     javafx.fxml.LoadException: Base location is undefined. loader.setLocation(SpringStageLoader.class.getResource(FXML_DIR + fxmlName + ".fxml")); // setLocation      loader     loader.setClassLoader(SpringStageLoader.class.getClassLoader()); loader.setControllerFactory(staticContext::getBean); return loader.load(SpringStageLoader.class.getResourceAsStream(FXML_DIR + fxmlName + ".fxml")); } /** *    .     ,     * @return   * @throws IOException   - */ public static Stage loadMain() throws IOException { Stage stage = new Stage(); stage.setScene(new Scene(load(MAIN_STAGE))); stage.setOnHidden(event -> Platform.exit()); stage.setTitle(staticTitle); return stage; } /** *          ApplicationContextAware, ..      */ @Override public void setApplicationContext(ApplicationContext context) throws BeansException { SpringStageLoader.staticContext = context; SpringStageLoader.staticTitle = appTitle; } } 


We slightly rewrite the start method in the Launcher class. We also add context initialization and its release.

Launcher.java
 package org.name.app; import javafx.application.Application; import javafx.stage.Stage; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.io.IOException; public class Launcher extends Application { private static ClassPathXmlApplicationContext context; public static void main(String[] args) { launch(args); } /** *   */ @Override public void init() { context = new ClassPathXmlApplicationContext("application-context.xml"); } @Override public void start(Stage stage) throws IOException { SpringStageLoader.loadMain().show(); } /** *   */ @Override public void stop() throws IOException { context.close(); } } 


Do not forget to inherit the MainController class from the Controller and all controllers to add the Component annotation, this allows you to add them to the context via component-scan and get any controllers from the context, like bins, or inject them. Otherwise, we get an exception
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.name.app.controller.MainController' available

We start and see that the text of the window title has become such that we have registered in the property:



But we have not yet downloaded the data loading as well as displaying detailed information about the product.

Implement the ProductDao class

ProductDao.java
 package org.name.model.dao; import org.name.model.Product; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.util.List; @Component public class ProductDao { private JdbcTemplate template; /** *  dataSource    JdbcTemplate */ @Autowired public ProductDao(DataSource dataSource) { this.template = new JdbcTemplate(dataSource); } /** *      . ..  Product    JavaBean *     BeanPropertyRowMapper. */ public List<Product> getAllProducts(){ String sql = "SELECT * FROM product"; return template.query(sql, new BeanPropertyRowMapper<>(Product.class)); } } 


Now it remains to add a couple of lines in the main controller, so that when you click on the button, the data is loaded into the table

MainController.java
 package org.name.app.controller; import javafx.fxml.FXML; import javafx.scene.control.Button; import org.name.model.dao.ProductDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MainController extends Controller { @FXML private Button load; private ProductTableController tableController; private ProductDao productDao; @Autowired public MainController(ProductTableController tableController, ProductDao productDao) { this.tableController = tableController; this.productDao = productDao; } /** *      */ @FXML public void onClickLoad() { tableController.fillTable(productDao.getAllProducts()); load.setDisable(true); } } 


and realize the opening of a new window with product details. To do this, use the productDetails template and the ProductDetailsModalStage scene.

productDetails.fxml
 <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1"> <children> <GridPane> <columnConstraints> <ColumnConstraints prefWidth="150.0"/> <ColumnConstraints prefWidth="300.0"/> </columnConstraints> <rowConstraints> <RowConstraints prefHeight="30.0"/> <RowConstraints prefHeight="30.0"/> <RowConstraints prefHeight="30.0"/> <RowConstraints prefHeight="30.0"/> <RowConstraints prefHeight="30.0"/> <RowConstraints prefHeight="30.0"/> </rowConstraints> <Label fx:id="name" style="-fx-font-weight: bold;-fx-padding: 3px;" prefWidth="450" GridPane.columnSpan="2" alignment="CENTER"/> <Label style="-fx-font-weight: bold; -fx-padding: 3px;" GridPane.rowIndex="1" text=":"/> <Label fx:id="guid" style="-fx-padding: 3px;" GridPane.rowIndex="1" GridPane.columnIndex="1"/> <Label style="-fx-font-weight: bold; -fx-padding: 3px;" GridPane.rowIndex="2" text="  :"/> <Label fx:id="quantity" style="-fx-padding: 3px;" GridPane.rowIndex="2" GridPane.columnIndex="1"/> <Label style="-fx-font-weight: bold; -fx-padding: 3px;" GridPane.rowIndex="3" text=":"/> <Label fx:id="price" style="-fx-padding: 3px;" GridPane.rowIndex="3" GridPane.columnIndex="1"/> <Label style="-fx-font-weight: bold; -fx-padding: 3px;" GridPane.rowIndex="4" text=" :"/> <Label fx:id="costOfAll" style="-fx-padding: 3px;" GridPane.rowIndex="4" GridPane.columnIndex="1"/> <Label style="-fx-font-weight: bold; -fx-padding: 3px;" GridPane.rowIndex="5" text=":"/> <Label fx:id="tax" style="-fx-padding: 3px;" GridPane.rowIndex="5" GridPane.columnIndex="1"/> </GridPane> </children> </AnchorPane> 


ProductDetailsModalStage.java
 package org.name.app.controller; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.stage.Modality; import javafx.stage.Stage; import org.name.app.SpringStageLoader; import org.name.model.Product; import java.io.IOException; public class ProductDetailsModalStage extends Stage { private Label name; private Label guid; private Label quantity; private Label price; private Label costOfAll; private Label tax; public ProductDetailsModalStage() { this.initModality(Modality.WINDOW_MODAL); this.centerOnScreen(); try { Scene scene = SpringStageLoader.loadScene("productDetails"); this.setScene(scene); name = (Label) scene.lookup("#name"); guid = (Label) scene.lookup("#guid"); quantity = (Label) scene.lookup("#quantity"); price = (Label) scene.lookup("#price"); costOfAll = (Label) scene.lookup("#costOfAll"); tax = (Label) scene.lookup("#tax"); } catch (IOException e) { e.printStackTrace(); } } public void showDetails(Product product) { name.setText(product.getName()); guid.setText(product.getGuid()); quantity.setText(String.valueOf(product.getQuantity())); price.setText(product.getPrice()); costOfAll.setText("$" + getCostOfAll(product)); tax.setText(String.valueOf(product.getTax()) + " %"); setTitle(" : " + product.getName()); show(); } private String getCostOfAll(Product product) { int quantity = product.getQuantity(); double priceOfOne = Double.parseDouble(product .getPrice() .replace("$", "")); return String.valueOf(quantity * priceOfOne); } } 


In SpringStageLoader, we add another method:

 public static Scene loadScene(String fxmlName) throws IOException { return new Scene(load(fxmlName)); } 

and add a few lines to the ProductTableController initialization method:

 productTable.setRowFactory(rf -> { TableRow<Product> row = new TableRow<>(); row.setOnMouseClicked(event -> { if (event.getClickCount() == 2 && (!row.isEmpty())) { ProductDetailsModalStage stage = new ProductDetailsModalStage(); stage.showDetails(row.getItem()); } }); return row; }); 

Run and see the result:



The problem of long context initialization


And here is another interesting topic. Suppose that your context is initialized for a long time, in this case, the user will not understand whether the application is running or not. Therefore, for clarity, you need to add a splash screen during context initialization.
The scene with the screen saver will be written in the usual way through FXMLLoader . Since the context is just at that time will be initialized. The initialization of a heavy context is simulated by calling Thread.sleep (10,000);

Template with a picture:

splash.fxml
 <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.image.Image?> <?import javafx.scene.image.ImageView?> <?import javafx.scene.layout.AnchorPane?> <AnchorPane xmlns="http://javafx.com/javafx" mouseTransparent="true"> <ImageView> <Image url="@/view/image/splash.png"/> </ImageView> </AnchorPane> 


Modified Launcher to download the application with the splash screen

Launcher.java
 package org.name.app; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.stage.StageStyle; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.io.IOException; public class Launcher extends Application { private static ClassPathXmlApplicationContext context; private Stage splashScreen; public static void main(String[] args) { launch(args); } /** *     UI .    init() UI    Platform.runLater() * @throws Exception */ @Override public void init() throws Exception { Platform.runLater(this::showSplash); Thread.sleep(10000); context = new ClassPathXmlApplicationContext("application-context.xml"); Platform.runLater(this::closeSplash); } @Override public void start(Stage stage) throws IOException { SpringStageLoader.loadMain().show(); } /** *   */ @Override public void stop() { context.close(); } /** *    .    */ private void showSplash() { try { splashScreen = new Stage(StageStyle.TRANSPARENT); splashScreen.setTitle("Splash"); Parent root = FXMLLoader.load(getClass().getResource("/view/fxml/splash.fxml")); Scene scene = new Scene(root, Color.TRANSPARENT); splashScreen.setScene(scene); splashScreen.show(); } catch (IOException e) { e.printStackTrace(); } } /** *     */ private void closeSplash() { splashScreen.close(); } } 


We collect, run and get what we wanted:

Launch App GIF


Final build jar


Stayed last step. This is to build a JAR, but with Spring `th. For this you need to add another maven-shade-plugin plugin to pom :

pom.xml - final version
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.name</groupId> <artifactId>SpringFXExample</artifactId> <version>1.0</version> <properties> <maven.compiler.source>9</maven.compiler.source> <maven.compiler.target>9</maven.compiler.target> <spring.version>5.0.3.RELEASE</spring.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>org.name.app.Launcher</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.7.2</version> </dependency> </dependencies> </project> 


In this simple way, you can make friends with Spring and JavaFX. Final project structure:

 SpringFXExample ├──.idea ├──src │ ├──main │ │ ├──java │ │ │ └──org.name │ │ │ ├──app │ │ │ │ ├──controller │ │ │ │ │ ├──Controller.java │ │ │ │ │ ├──MainController.java │ │ │ │ │ ├──ProductTableController.java │ │ │ │ │ └──ProductDetailsModalStage.java │ │ │ │ ├──Launcher.java │ │ │ │ └──SpringStageLoader.java │ │ │ └──model │ │ │ ├──dao │ │ │ │ └─ProductDao.java │ │ │ └──Product.java │ │ └──resources │ │ ├──view │ │ │ ├──fxml │ │ │ │ ├──main.fxml │ │ │ │ ├──productDetails.fxml │ │ │ │ ├──productTable.fxml │ │ │ │ └──splash.fxml │ │ │ ├──style │ │ │ └──image │ │ │ └──splash.png │ │ └──application-context.xml │ └──test ├──────config.properties.xml ├──────pom.xml ├──────SpringFXExample.iml └──────test-db.xml External Libraries 

Sources on github . There is also a PRODUCTS.sql file for the table in the database.

Source: https://habr.com/ru/post/348850/


All Articles