JavaFX и Spring. Вместе веселей

от автора

В данной статье я хочу рассказать о своем опыте интеграции таких вещей как JavaFX и Spring. И заодно использовать базу данных Derby и Maven для сборки приложения.

Введение

JavaFX выглядит довольно удобной и привлекательной технологией для реализации десктопных решений на платформе Java. Начиная с версии Java SE 7 Update 6, JavaFX является частью реализации Oracle Java SE, т.е. никаких дополнительных установок на стороне пользователя не требуется.

Spring со своей стороны, дает удобные фишечки в виде IoC, управление транзакциями и т.д., которые не хочется реализовывать самому.

Hello world

Начнем с простого приложения использующего FXML.

Класс приложения:

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();     } } 

В данном коде у нас есть класс TodoApplication, которое является точкой входа для JavaFX приложения. С помощью FXMLLoader’а мы загружаем необходимый View из ресурсов. Загрузчик инициализирует вместе с View так же и контроллер.

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> 

В общем ничего особенного, можно ехать дальше.

Собираем с помощью Maven

Для сборки можно использовать специальный плагин от Zen Java. Помимо сборки JavaFX приложения, он умеет собирать нативные установщики для него (MSI, EXE, DMG, RPM) вместе с JRE.

Пример 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> 

Как видно, в конфигурации плагина нужно указать путь к главному классу приложения. Но это еще не все, так же необходимо перед запуском приложения выполнить следующую команду:

mvn com.zenjava:javafx-maven-plugin:2.0:fix-classpath 

Подробно о том зачем это можно почитать в документации плагина.

Подключаем Derby

Для полного счастья нам не хватает полноценной БД в нашем приложении.

Нужно добавить зависимости для управления сервисом Derby и драйвер для доступа к БД:

<?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> 

Немного модифицируем класс TodoApplication так, что бы он запускал и останавливал БД.

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);         }     } } 

Сам класс DbUtils:

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();     } } 

Добавляем Spring

Теперь добавляем нужные зависимости для Spring, а заодно и Hibernate в 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> 

Нам необходимо реализовать свой загрузчик, который будет отвечает за загрузку контроллеров и View-компонентов для них:

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);                 }             }         }     } } 

Как видно в качестве фабрики контроллеров используется контекст приложения Spring’a. Мы загружаем сначала по URL необходимую View, после загружаем соответствующий контроллер.

Пример 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;     } } 

Для наших контроллеров мы используем следующий интерфейс, который позволяет связывать контроллер и вид:

package ru.todolist.controller; import javafx.scene.Node;  public interface Controller {     Node getView();     void setView (Node view); } 

Вынесем реализацию этих методов в абстрактный класс 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;     } } 

И последний штрих, используем SprinFXMLLoader взамен стандартного загрузчика в классе TodoApplication:

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();     } ... } 

Итоги

Код получился довольно простым, без особых извращений. В результате мы можем использовать JavaFX с привычным стеком технологий (для Java EE) и использовать привычные паттерны для проектирования архитектуры приложения.

В качестве дополнения хочется сказать, что можно использовать данный подход и для интеграции с Guice.

Ресурсы

ссылка на оригинал статьи http://habrahabr.ru/post/203960/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *