JavaFX and Spring. It's more fun together
In this article, I want to talk about my experience 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 pretty 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 installation is required on the user side.
Spring, for its part, provides convenient features in the form of IoC, transaction management, etc., which you do not 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 the JavaFX application. Using FXMLLoader, we load the necessary View from the resources. The loader also initializes the controller along with the View.
main.fxml:
In general, nothing special, you can go further.
Build with Maven
For assembly, you can use a special plug-in from Zen Java . In addition to building a JavaFX application, he can build native installers for it (MSI, EXE, DMG, RPM) along with the JRE.
Example pom.xml:
4.0.0 ru.todolist application jar 1.0.0 1.2.17 log4j log4j ${log4j.version} com.zenjava javafx-maven-plugin 2.0 ru.todolist.TodoApplication
As you can see, in the plugin configuration you need to specify the path to the main class of the application. But this is not all, it is also necessary to run the following command before starting the application:
mvn com.zenjava:javafx-maven-plugin:2.0:fix-classpath
Details on why this can be found in the plugin documentation .
We connect Derby
For complete happiness, we lack a full-fledged database in our application.
You need to add dependencies for managing the Derby service and a driver for accessing the database:
4.0.0 ru.todolist application jar 1.0.0 10.10.1.1
...
org.apache.derby derbynet ${derby.version} org.apache.derby derbyclient ${derby.version}
...
...
We modify the TodoApplication class a little 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 along with Hibernate in pom.xml:
4.0.0 ru.todolist application jar 1.0.0
...
3.2.4.RELEASE 4.2.6.Final org.hibernate hibernate-entitymanager ${hibernate.version} org.springframework spring-core ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-orm ${spring.version}
...
...
We need to implement our loader, which will be responsible for loading the 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, 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. First, we load the necessary View from the URL, then we load the corresponding controller.
AppConfig.java example
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 bind the controller and view:
package ru.todolist.controller;
import javafx.scene.Node;
public interface Controller {
Node getView();
void setView (Node view);
}
Take out the implementation of these methods in the abstract class AbstractController.java:
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();
}
...
}
Summary
The code turned out to be quite simple, without much perversion. As a result, we can use JavaFX with the familiar technology stack (for Java EE) and use familiar patterns to design the application architecture.
As an addition, I want to say that you can use this approach for integration with Guice.