📜 ⬆️ ⬇️

How to make friends JavaFX and Spring Boot


Not so long ago, there was such a wonderful framework as Spring Boot, without which I can’t imagine Java development anymore. Illuminating the unlit, I want to consider the integration of Spring Boot and all its "buns" with JavaFX 2.

All interested invite under the cat.

Preabmula


Spring Boot is a wonderful framework, without which it is impossible to do without trying once (I recommend everyone to do it! ). I want to touch on a topic that is not quite trivial for it, namely, integration with JavaFX. Well, so that it was not boring, I will write a simple guide with blackjack and ... connection to the database.

Let's get started


The configuration of the Maven project is no different from the usual Spring Boot application.
')
pom.xml
<dependencies> <!-- Spring Boot starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- Spring Boot JPA -    ,  --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- H2  --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!--  --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> 

The application settings file is also nothing special.

application.properties
 #  UI ui.title = Spring Boot - JavaFX # JMX   ,       spring.jmx.enabled=false #      JPA spring.datasource.test-on-borrow=true spring.datasource.validation-query=SELECT 1 spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create 

But with the entry point into the application, everything is much more interesting!
We need to initialize the Spring context and do it in two different places:

We write the abstract class as follows:

AbstractJavaFxApplicationSupport.java
 package ru.habrahabr; import javafx.application.Application; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; public abstract class AbstractJavaFxApplicationSupport extends Application { private static String[] savedArgs; protected ConfigurableApplicationContext context; @Override public void init() throws Exception { context = SpringApplication.run(getClass(), savedArgs); context.getAutowireCapableBeanFactory().autowireBean(this); } @Override public void stop() throws Exception { super.stop(); context.close(); } protected static void launchApp(Class<? extends AbstractJavaFxApplicationSupport> clazz, String[] args) { AbstractJavaFxApplicationSupport.savedArgs = args; Application.launch(clazz, args); } } 

I want to draw attention to the overridden init () method.
It is at the time of initialization of JavaFX that we are launching the initialization of the Spring context:

 context = SpringApplication.run(getClass(), savedArgs); 

Well, the next line fills the current object with bins:

 context.getAutowireCapableBeanFactory().autowireBean(this); 

Inheriting the abstract class described above, we specify the behavior of our JavaFX application. At this stage, we can already use DI and all the other "buns" of the spring:

Application.java
 @Lazy @SpringBootApplication public class Application extends AbstractJavaFxApplicationSupport { @Value("${ui.title:JavaFX }")// private String windowTitle; @Autowired private ControllersConfig.View view; @Override public void start(Stage stage) throws Exception { stage.setTitle(windowTitle); stage.setScene(new Scene(view.getParent())); stage.setResizable(true); stage.centerOnScreen(); stage.show(); } public static void main(String[] args) { launchApp(Application.class, args); } } 

Well, now to the fun part.
JavaFX provides the ability to separate code (controller) and view (view), and the view is stored in XML format, in a file with the extension * .fxml. For the view itself there is a wonderful UI editor - Scene Builder .
I got something like this view file:

main.fxml
 <?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="284.0" prefWidth="405.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ru.habrahabr.ui.MainController"> <children> <TableView fx:id="table" editable="true" prefHeight="200.0" prefWidth="405.0" tableMenuButtonVisible="true" AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /></columnResizePolicy> </TableView> <HBox alignment="CENTER" layoutX="21.0" layoutY="207.0" prefHeight="50.0" prefWidth="300.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0"> <children> <TextField fx:id="txtName" promptText=""> <HBox.margin> <Insets right="3.0" /> </HBox.margin> </TextField> <TextField fx:id="txtPhone" promptText=""> <HBox.margin> <Insets right="3.0" /> </HBox.margin> </TextField> <TextField fx:id="txtEmail" promptText="E-mail"> <HBox.margin> <Insets right="3.0" /> </HBox.margin> </TextField> <Button minWidth="-Infinity" mnemonicParsing="false" onAction="#addContact" text="" /> </children> </HBox> </children> </AnchorPane> 

The listing of this file is hard to read, but note that the root element has the attribute fx: controller = "ru.habrahabr.ui.MainController" . It indicates which controller class to use for this view component. And for nested elements, the fx: id = "txtEmail" attribute indicates which field of the controller to inject. The problem is to make friends the injections of the controller from JavaFX (which are defined by the @FXML annotation) and the injections from the spring. Because, if you use the standard FXML loader, then the spring will not know about the new controller object, and, accordingly, it will not make its injections.
We write the controller itself:

MainController.java
 package ru.habrahabr.ui; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import ru.habrahabr.entity.Contact; import ru.habrahabr.service.ContactService; import javax.annotation.PostConstruct; import java.util.List; public class MainController { //  Spring @Autowired private ContactService contactService; //  JavaFX @FXML private TableView<Contact> table; @FXML private TextField txtName; @FXML private TextField txtPhone; @FXML private TextField txtEmail; //  private ObservableList<Contact> data; /** *    JavaFX. *      FXML    . * *  ,    <b></b>   "initialize", *   ,   . * *         *      , *   @PostConstruct. *   ,  , *       . * {@link MainController#init()} */ @FXML public void initialize() { } /** *        . */ @PostConstruct public void init() { List<Contact> contacts = contactService.findAll(); data = FXCollections.observableArrayList(contacts); //     TableColumn<Contact, String> idColumn = new TableColumn<>("ID"); idColumn.setCellValueFactory(new PropertyValueFactory<>("id")); TableColumn<Contact, String> nameColumn = new TableColumn<>(""); nameColumn.setCellValueFactory(new PropertyValueFactory<>("name")); TableColumn<Contact, String> phoneColumn = new TableColumn<>(""); phoneColumn.setCellValueFactory(new PropertyValueFactory<>("phone")); TableColumn<Contact, String> emailColumn = new TableColumn<>("E-mail"); emailColumn.setCellValueFactory(new PropertyValueFactory<>("email")); table.getColumns().setAll(idColumn, nameColumn, phoneColumn, emailColumn); //     table.setItems(data); } /** * ,      "". *     FXML  . */ @FXML public void addContact() { Contact contact = new Contact(txtName.getText(), txtPhone.getText(), txtEmail.getText()); contactService.save(contact); data.add(contact); //   txtName.setText(""); txtPhone.setText(""); txtEmail.setText(""); } } 

It remains to figure out how we managed to get Spring to make his injections in an unfamiliar object. And the secret lies in another Spring Boot configuration class:

ConfigurationControllers.java
 @Configuration public class ConfigurationControllers { @Bean(name = "mainView") public View getMainView() throws IOException { return loadView("fxml/main.fxml"); } /** *          , *       . */ @Bean public MainController getMainController() throws IOException { return (MainController) getMainView().getController(); } /** *     FXML . *  -      -, *   FXML      . */ protected View loadView(String url) throws IOException { InputStream fxmlStream = null; try { fxmlStream = getClass().getClassLoader().getResourceAsStream(url); FXMLLoader loader = new FXMLLoader(); loader.load(fxmlStream); return new View(loader.getRoot(), loader.getController()); } finally { if (fxmlStream != null) { fxmlStream.close(); } } } /** *  - :       , *  view - ,       {@link Application}. */ public class View { private Parent view; private Object controller; public View(Parent view, Object controller) { this.view = view; this.controller = controller; } public Parent getView() { return view; } public void setView(Parent view) { this.view = view; } public Object getController() { return controller; } public void setController(Object controller) { this.controller = controller; } } } 

That's all, we got a JavaFX application integrated with Spring Boot, which opens up all its great features.



Link to sources: github.com/ruslanys/sample-springboot-javafx

PS I would be happy if this post is useful to someone. I would be grateful for tips and corrections.

I want to thank xolvo for making me try Spring Boot and Stephen Chin, for this excellent article describing all the subtleties of JavaFX and Spring Boot integration.

UPD. Thanks ISergius for editing.

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


All Articles