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.0ru.todolistapplicationjar1.0.01.2.17log4jlog4j${log4j.version}com.zenjavajavafx-maven-plugin2.0ru.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.0ru.todolistapplicationjar1.0.010.10.1.1		
    		...
        org.apache.derbyderbynet${derby.version}org.apache.derbyderbyclient${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.0ru.todolistapplicationjar1.0.0
    		...
            3.2.4.RELEASE4.2.6.Finalorg.hibernatehibernate-entitymanager${hibernate.version}org.springframeworkspring-core${spring.version}org.springframeworkspring-context${spring.version}org.springframeworkspring-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.

    Resources



    Also popular now: