📜 ⬆️ ⬇️

JavaFX and Spring. It's more fun together



In this article, I want to talk about my experience of integrating things like JavaFX and Spring. And at the same time use the Derby and Maven database to build the application.

Introduction


JavaFX looks like a rather convenient and attractive technology for implementing desktop solutions on the Java platform. Starting with Java SE 7 Update 6, JavaFX is part of the Oracle Java SE implementation, i.e. no additional installations on the user side are required.
')
Spring, for its part, provides convenient features in the form of IoC, transaction management, etc., which you don’t want to implement yourself.


Hello world


Let's start with a simple application using FXML.

Application class:
package ru.todolist; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class TodoApplication extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("/fxml/main.fxml")); Scene scene = new Scene(root, 300, 275); primaryStage.setTitle("Todolist"); primaryStage.setScene(scene); primaryStage.show(); } } 


In this code, we have the TodoApplication class, which is the entry point for a JavaFX application. With the help of FXMLLoader, we load the required View from resources. The loader initializes along with the View also the controller.

main.fxml:
 <?xml version="1.0" encoding="UTF-8"?> <?import java.net.*?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.text.*?> <GridPane fx:controller="ru.todolist.controller.MainController" xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10" styleClass="root"> <Text id="welcome-text" text="Hello world!" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2"/> </GridPane> 


In general, nothing special, you can go further.

We collect by means of Maven


To build, you can use a special plugin from Zen Java . In addition to building a JavaFX application, he can build native installers for him (MSI, EXE, DMG, RPM) along with the JRE.

Example 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>ru.todolist</groupId> <artifactId>application</artifactId> <packaging>jar</packaging> <version>1.0.0</version> <properties> <log4j.version>1.2.17</log4j.version> </properties> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>com.zenjava</groupId> <artifactId>javafx-maven-plugin</artifactId> <version>2.0</version> <configuration> <mainClass>ru.todolist.TodoApplication</mainClass> </configuration> </plugin> </plugins> </build> </project> 

As you can see, in the configuration of the plugin you need to specify the path to the main class of the application. But that's not all, it is also necessary to run the following command before launching the application:
 mvn com.zenjava:javafx-maven-plugin:2.0:fix-classpath 

Details on why this can be read in the plug-in documentation .

We connect Derby


For complete happiness, we lack a complete database in our application.

You need to add dependencies to control the Derby service and a driver to access the database:
 <?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>ru.todolist</groupId> <artifactId>application</artifactId> <packaging>jar</packaging> <version>1.0.0</version> <properties> <derby.version>10.10.1.1</derby.version> ... </properties> <dependencies> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbynet</artifactId> <version>${derby.version}</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbyclient</artifactId> <version>${derby.version}</version> </dependency> ... </dependencies> <build> ... </build> </project> 


We slightly modify the TodoApplication class so that it starts and stops the database.
 public class TodoApplication extends Application { private static Logger LOG = Logger.getLogger(TodoApplication.class); ... @Override public void init() { try { DbUtils.startDB(); } catch (Exception e) { LOG.error("Problem with start DB", e); } } @Override public void stop() { try { DbUtils.stopDB(); } catch (Exception e) { LOG.error("Problem with stop DB", e); } } } 


The DbUtils class itself:
 package ru.todolist.utils; import org.apache.derby.drda.NetworkServerControl; import org.apache.log4j.Logger; import java.net.InetAddress; public class DbUtils { private static Logger LOG = Logger.getLogger(DbUtils.class); private static NetworkServerControl server; public static void startDB() throws Exception { LOG.info("Start DB"); server = new NetworkServerControl(InetAddress.getByName("localhost"), 1527); server.start(null); } public static void stopDB() throws Exception { LOG.info("Stop DB"); server.shutdown(); } } 


Add Spring


Now we add the necessary dependencies for Spring, and at the same time Hibernate in 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>ru.todolist</groupId> <artifactId>application</artifactId> <packaging>jar</packaging> <version>1.0.0</version> <properties> ... <spring.version>3.2.4.RELEASE</spring.version> <hibernate.version>4.2.6.Final</hibernate.version> </properties> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> ... </dependencies> <build> ... </build> </project> 


We need to implement our own loader, which will be responsible for loading controllers and View components for them:
 package ru.todolist.utils; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.util.Callback; import org.apache.log4j.Logger; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import ru.todolist.config.AppConfig; import ru.todolist.controller.Controller; import java.io.IOException; import java.io.InputStream; public class SpringFXMLLoader { private static Logger LOG = Logger.getLogger(SpringFXMLLoader.class); private static final ApplicationContext APPLICATION_CONTEXT = new AnnotationConfigApplicationContext(AppConfig.class); public static Controller load(String url) { InputStream fxmlStream = null; try { fxmlStream = SpringFXMLLoader.class.getResourceAsStream(url); FXMLLoader loader = new FXMLLoader(); loader.setControllerFactory(new Callback<Class<?>, Object>() { @Override public Object call(Class<?> aClass) { return APPLICATION_CONTEXT.getBean(aClass); } }); Node view = (Node) loader.load(fxmlStream); Controller controller = loader.getController(); controller.setView(view); return controller; } catch (IOException e) { LOG.error("Can't load resource", e); throw new RuntimeException(e); } finally { if (fxmlStream != null) { try { fxmlStream.close(); } catch (IOException e) { LOG.error("Can't close stream", e); } } } } } 


As you can see, the Spring application context is used as the controller factory. We first load the required View at the URL, then load the corresponding controller.

Example AppConfig.java
 package ru.todolist.config; import org.hibernate.ejb.HibernatePersistence; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import java.util.Properties; @Configuration @ComponentScan("ru.todolist") public class AppConfig { @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.apache.derby.jdbc.ClientDriver"); dataSource.setUrl("jdbc:derby://localhost:1527/todo;create=true"); //Create DB if not exist dataSource.setUsername("user"); dataSource.setPassword("password"); return dataSource; } @Autowired @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource dataSource) { LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean(); Properties properties = new Properties(); properties.put("hibernate.dialect", "org.hibernate.dialect.DerbyDialect"); properties.put("hibernate.hbm2ddl.auto", "create"); bean.setPersistenceProviderClass(HibernatePersistence.class); bean.setDataSource(dataSource); bean.setJpaProperties(properties); bean.setPackagesToScan("ru.todolist.model"); return bean; } @Autowired @Bean public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory, DataSource dataSource) { JpaTransactionManager bean = new JpaTransactionManager(entityManagerFactory); bean.setDataSource(dataSource); return bean; } } 



For our controllers, we use the following interface, which allows you to link the controller and the view:
 package ru.todolist.controller; import javafx.scene.Node; public interface Controller { Node getView(); void setView (Node view); } 


We will release the implementation of these methods to the AbstractController.java abstract class:
 package ru.todolist.controller; import javafx.scene.Node; public abstract class AbstractController implements Controller { private Node view; public Node getView() { return view; } public void setView (Node view){ this.view = view; } } 


And the final touch, we use SprinFXMLLoader instead of the standard loader in the TodoApplication class:
 public class TodoApplication extends Application { ... @Override public void start(Stage primaryStage) throws Exception { MainController controller = (MainController) SpringFXMLLoader.load("/fxml/main.fxml"); Scene scene = new Scene((Parent) controller.getView(), 300, 275); primaryStage.setTitle("Todolist"); primaryStage.setScene(scene); primaryStage.show(); } ... } 


Results


The code is pretty simple, without much perversion. As a result, we can use JavaFX with a familiar technology stack (for Java EE) and use familiar patterns to design the application architecture.

In addition, I would like to say that you can use this approach to integrate with Guice.

Resources


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


All Articles