├── .gitignore ├── README.md ├── pom.xml ├── screen.png └── src ├── main ├── java │ └── ru │ │ └── habrahabr │ │ ├── AbstractJavaFxApplicationSupport.java │ │ ├── Application.java │ │ ├── ControllersConfiguration.java │ │ ├── entity │ │ ├── Contact.java │ │ └── ContactWithProperties.java │ │ ├── repository │ │ └── ContactRepository.java │ │ ├── service │ │ ├── ContactService.java │ │ └── ContactServiceImpl.java │ │ └── ui │ │ └── MainController.java └── resources │ ├── application.properties │ └── fxml │ └── main.fxml └── test └── java └── ru └── habrahabr └── ApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Как подружить Spring Boot и JavaFX 2 | http://habrahabr.ru/post/265511/ 3 | 4 | ### Сборка 5 | ``` 6 | mvn clean package 7 | ``` 8 | 9 | ### Запуск 10 | ``` 11 | java -jar target/springboot-javafx-*.jar 12 | ``` 13 | 14 | ![Скриншот](screen.png "Скриншот") 15 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | ru.habrahabr 7 | springboot-javafx 8 | 0.0.1 9 | jar 10 | 11 | spring-boot-javafx 12 | Как подружить Spring Boot и JavaFX 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.2.5.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-data-jpa 39 | 40 | 41 | 42 | com.h2database 43 | h2 44 | runtime 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-test 53 | test 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruslanys/sample-spring-boot-javafx/b28e0e6c09b555645fb98028831e4348cc87ab4e/screen.png -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/AbstractJavaFxApplicationSupport.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr; 2 | 3 | import javafx.application.Application; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.context.ConfigurableApplicationContext; 6 | 7 | public abstract class AbstractJavaFxApplicationSupport extends Application { 8 | 9 | private static String[] savedArgs; 10 | 11 | protected ConfigurableApplicationContext context; 12 | 13 | @Override 14 | public void init() throws Exception { 15 | context = SpringApplication.run(getClass(), savedArgs); 16 | context.getAutowireCapableBeanFactory().autowireBean(this); 17 | } 18 | 19 | @Override 20 | public void stop() throws Exception { 21 | super.stop(); 22 | context.close(); 23 | } 24 | 25 | protected static void launchApp(Class appClass, String[] args) { 26 | AbstractJavaFxApplicationSupport.savedArgs = args; 27 | Application.launch(appClass, args); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/Application.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr; 2 | 3 | import javafx.scene.Scene; 4 | import javafx.stage.Stage; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | 10 | @SpringBootApplication 11 | public class Application extends AbstractJavaFxApplicationSupport { 12 | 13 | @Value("${ui.title:JavaFX приложение}")// 14 | private String windowTitle; 15 | 16 | @Qualifier("mainView") 17 | @Autowired 18 | private ControllersConfiguration.ViewHolder view; 19 | 20 | @Override 21 | public void start(Stage stage) throws Exception { 22 | stage.setTitle(windowTitle); 23 | stage.setScene(new Scene(view.getView())); 24 | stage.setResizable(true); 25 | stage.centerOnScreen(); 26 | stage.show(); 27 | } 28 | 29 | public static void main(String[] args) { 30 | launchApp(Application.class, args); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/ControllersConfiguration.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr; 2 | 3 | import javafx.fxml.FXMLLoader; 4 | import javafx.scene.Parent; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import ru.habrahabr.ui.MainController; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | /** 13 | * Date: 27.08.15 14 | * Time: 11:04 15 | * 16 | * @author Ruslan Molchanov (ruslanys@gmail.com) 17 | * @author http://mruslan.com 18 | */ 19 | @Configuration 20 | public class ControllersConfiguration { 21 | 22 | @Bean(name = "mainView") 23 | public ViewHolder getMainView() throws IOException { 24 | return loadView("fxml/main.fxml"); 25 | } 26 | 27 | /** 28 | * Именно благодаря этому методу мы добавили контроллер в контекст спринга, 29 | * и заставили его сделать произвести все необходимые инъекции. 30 | */ 31 | @Bean 32 | public MainController getMainController() throws IOException { 33 | return (MainController) getMainView().getController(); 34 | } 35 | 36 | /** 37 | * Самый обыкновенный способ использовать FXML загрузчик. 38 | * Как раз-таки на этом этапе будет создан объект-контроллер, 39 | * произведены все FXML инъекции и вызван метод инициализации контроллера. 40 | */ 41 | protected ViewHolder loadView(String url) throws IOException { 42 | InputStream fxmlStream = null; 43 | try { 44 | fxmlStream = getClass().getClassLoader().getResourceAsStream(url); 45 | FXMLLoader loader = new FXMLLoader(); 46 | loader.load(fxmlStream); 47 | return new ViewHolder(loader.getRoot(), loader.getController()); 48 | } finally { 49 | if (fxmlStream != null) { 50 | fxmlStream.close(); 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * Класс - оболочка: контроллер мы обязаны указать в качестве бина, 57 | * а view - представление, нам предстоит использовать в точке входа {@link Application}. 58 | */ 59 | public class ViewHolder { 60 | private Parent view; 61 | private Object controller; 62 | 63 | public ViewHolder(Parent view, Object controller) { 64 | this.view = view; 65 | this.controller = controller; 66 | } 67 | 68 | public Parent getView() { 69 | return view; 70 | } 71 | 72 | public void setView(Parent view) { 73 | this.view = view; 74 | } 75 | 76 | public Object getController() { 77 | return controller; 78 | } 79 | 80 | public void setController(Object controller) { 81 | this.controller = controller; 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/entity/Contact.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr.entity; 2 | 3 | import javax.persistence.*; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Date: 27.08.15 8 | * Time: 12:58 9 | * 10 | * @author Ruslan Molchanov (ruslanys@gmail.com) 11 | * @author http://mruslan.com 12 | */ 13 | @Entity 14 | @Table 15 | public class Contact implements Serializable { 16 | 17 | @Id 18 | @GeneratedValue 19 | private Long id; 20 | 21 | @Column(nullable = false) 22 | private String name; 23 | 24 | @Column(nullable = false, unique = true) 25 | private String phone; 26 | 27 | @Column(nullable = false, unique = true) 28 | private String email; 29 | 30 | public Contact() { 31 | } 32 | 33 | public Contact(String name, String phone, String email) { 34 | this.name = name; 35 | this.phone = phone; 36 | this.email = email; 37 | } 38 | 39 | public Long getId() { 40 | return id; 41 | } 42 | 43 | public void setId(Long id) { 44 | this.id = id; 45 | } 46 | 47 | public String getName() { 48 | return name; 49 | } 50 | 51 | public void setName(String name) { 52 | this.name = name; 53 | } 54 | 55 | public String getPhone() { 56 | return phone; 57 | } 58 | 59 | public void setPhone(String phone) { 60 | this.phone = phone; 61 | } 62 | 63 | public String getEmail() { 64 | return email; 65 | } 66 | 67 | public void setEmail(String email) { 68 | this.email = email; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/entity/ContactWithProperties.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr.entity; 2 | 3 | import javafx.beans.property.LongProperty; 4 | import javafx.beans.property.SimpleLongProperty; 5 | import javafx.beans.property.SimpleStringProperty; 6 | import javafx.beans.property.StringProperty; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | import java.io.Serializable; 12 | 13 | /** 14 | * Класс, аналогичный классу {@link Contact} за тем исключением, 15 | * что поля из себя представляют JavaFX Property. Это может пригодиться. 16 | * 17 | * JPA с ним работает аналогично классу {@link Contact}. 18 | */ 19 | //@Entity 20 | //@Table 21 | //@Access(AccessType.PROPERTY) 22 | public class ContactWithProperties implements Serializable { 23 | 24 | private LongProperty id = new SimpleLongProperty(); 25 | 26 | @Column 27 | private StringProperty name = new SimpleStringProperty(); 28 | 29 | @Column(unique = true) 30 | private StringProperty phone = new SimpleStringProperty(); 31 | 32 | @Column(unique = true) 33 | private StringProperty email = new SimpleStringProperty(); 34 | 35 | public ContactWithProperties() { 36 | } 37 | 38 | public ContactWithProperties(Long id, String name, String phone, String email) { 39 | setId(id); 40 | setName(name); 41 | setPhone(phone); 42 | setEmail(email); 43 | } 44 | 45 | @Id 46 | @GeneratedValue 47 | public Long getId() { 48 | return id.get(); 49 | } 50 | 51 | public LongProperty idProperty() { 52 | return id; 53 | } 54 | 55 | public void setId(Long id) { 56 | this.id.set(id); 57 | } 58 | 59 | public String getName() { 60 | return name.get(); 61 | } 62 | 63 | public StringProperty nameProperty() { 64 | return name; 65 | } 66 | 67 | public void setName(String name) { 68 | this.name.set(name); 69 | } 70 | 71 | public String getPhone() { 72 | return phone.get(); 73 | } 74 | 75 | public StringProperty phoneProperty() { 76 | return phone; 77 | } 78 | 79 | public void setPhone(String phone) { 80 | this.phone.set(phone); 81 | } 82 | 83 | public String getEmail() { 84 | return email.get(); 85 | } 86 | 87 | public StringProperty emailProperty() { 88 | return email; 89 | } 90 | 91 | public void setEmail(String email) { 92 | this.email.set(email); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/repository/ContactRepository.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springframework.transaction.annotation.Propagation; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import ru.habrahabr.entity.Contact; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Date: 27.08.15 12 | * Time: 17:21 13 | * 14 | * @author Ruslan Molchanov (ruslanys@gmail.com) 15 | * @author http://mruslan.com 16 | */ 17 | @Transactional(propagation = Propagation.MANDATORY) 18 | public interface ContactRepository extends CrudRepository { 19 | 20 | List findAll(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/service/ContactService.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr.service; 2 | 3 | import ru.habrahabr.entity.Contact; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Date: 27.08.15 9 | * Time: 17:22 10 | * 11 | * @author Ruslan Molchanov (ruslanys@gmail.com) 12 | * @author http://mruslan.com 13 | */ 14 | public interface ContactService { 15 | 16 | Contact save(Contact contact); 17 | 18 | List findAll(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/service/ContactServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import ru.habrahabr.entity.Contact; 6 | import ru.habrahabr.repository.ContactRepository; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.transaction.Transactional; 10 | import java.util.List; 11 | 12 | /** 13 | * Date: 27.08.15 14 | * Time: 17:23 15 | * 16 | * @author Ruslan Molchanov (ruslanys@gmail.com) 17 | * @author http://mruslan.com 18 | */ 19 | @Service 20 | @Transactional 21 | public class ContactServiceImpl implements ContactService { 22 | 23 | private final ContactRepository repository; 24 | 25 | @Autowired 26 | public ContactServiceImpl(ContactRepository repository) { 27 | this.repository = repository; 28 | } 29 | 30 | /** 31 | * Метод добавляет парочку записей в БД после запуска приложения, 32 | * чтобы не было совсем пусто. 33 | * 34 | * Из-за того, что подключена H2 (in-memory) БД. 35 | */ 36 | @PostConstruct 37 | public void generateTestData() { 38 | save(new Contact("Иван Иванов", "+123456789", "ivan@ivan.ov")); 39 | save(new Contact("Петр Петров", "+987654321", "petr@pe.tr")); 40 | } 41 | 42 | @Override 43 | public Contact save(Contact contact) { 44 | return repository.save(contact); 45 | } 46 | 47 | @Override 48 | public List findAll() { 49 | return repository.findAll(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/ru/habrahabr/ui/MainController.java: -------------------------------------------------------------------------------- 1 | package ru.habrahabr.ui; 2 | 3 | import javafx.collections.FXCollections; 4 | import javafx.collections.ObservableList; 5 | import javafx.fxml.FXML; 6 | import javafx.scene.control.TableColumn; 7 | import javafx.scene.control.TableView; 8 | import javafx.scene.control.TextField; 9 | import javafx.scene.control.cell.PropertyValueFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.util.StringUtils; 12 | import ru.habrahabr.entity.Contact; 13 | import ru.habrahabr.service.ContactService; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.util.List; 17 | 18 | /** 19 | * Date: 27.08.15 20 | * Time: 11:10 21 | * 22 | * @author Ruslan Molchanov (ruslanys@gmail.com) 23 | * @author http://mruslan.com 24 | */ 25 | @SuppressWarnings("SpringJavaAutowiringInspection") 26 | public class MainController { 27 | 28 | // Инъекции Spring 29 | @Autowired private ContactService contactService; 30 | 31 | // Инъекции JavaFX 32 | @FXML private TableView table; 33 | @FXML private TextField txtName; 34 | @FXML private TextField txtPhone; 35 | @FXML private TextField txtEmail; 36 | 37 | // Variables 38 | private ObservableList data; 39 | 40 | /** 41 | * Инициализация контроллера от JavaFX. 42 | * Метод вызывается после того как FXML загрузчик произвел инъекции полей. 43 | * 44 | * Обратите внимание, что имя метода обязательно должно быть "initialize", 45 | * в противном случае, метод не вызовется. 46 | * 47 | * Также на этом этапе еще отсутствуют бины спринга 48 | * и для инициализации лучше использовать метод, 49 | * описанный аннотацией @PostConstruct, 50 | * который вызовется спрингом, после того, как им будут произведены все инъекции. 51 | * {@link MainController#init()} 52 | */ 53 | @FXML 54 | public void initialize() { 55 | // Этап инициализации JavaFX 56 | } 57 | 58 | /** 59 | * На этом этапе уже произведены все возможные инъекции. 60 | */ 61 | @SuppressWarnings("unchecked") 62 | @PostConstruct 63 | public void init() { 64 | List contacts = contactService.findAll(); 65 | data = FXCollections.observableArrayList(contacts); 66 | 67 | // Столбцы таблицы 68 | TableColumn idColumn = new TableColumn<>("ID"); 69 | idColumn.setCellValueFactory(new PropertyValueFactory<>("id")); 70 | 71 | TableColumn nameColumn = new TableColumn<>("Имя"); 72 | nameColumn.setCellValueFactory(new PropertyValueFactory<>("name")); 73 | 74 | TableColumn phoneColumn = new TableColumn<>("Телефон"); 75 | phoneColumn.setCellValueFactory(new PropertyValueFactory<>("phone")); 76 | 77 | TableColumn emailColumn = new TableColumn<>("E-mail"); 78 | emailColumn.setCellValueFactory(new PropertyValueFactory<>("email")); 79 | 80 | table.getColumns().setAll(idColumn, nameColumn, phoneColumn, emailColumn); 81 | 82 | // Данные таблицы 83 | table.setItems(data); 84 | } 85 | 86 | /** 87 | * Метод, вызываемый при нажатии на кнопку "Добавить". 88 | * Привязан к кнопке в FXML файле представления. 89 | */ 90 | @FXML 91 | public void addContact() { 92 | String name = txtName.getText(); 93 | String phone = txtPhone.getText(); 94 | String email = txtEmail.getText(); 95 | if (StringUtils.isEmpty(name) || StringUtils.isEmpty(phone) || StringUtils.isEmpty(email)) { 96 | return; 97 | } 98 | 99 | Contact contact = new Contact(name, phone, email); 100 | contactService.save(contact); 101 | data.add(contact); 102 | 103 | // чистим поля 104 | txtName.setText(""); 105 | txtPhone.setText(""); 106 | txtEmail.setText(""); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Параметры UI 2 | ui.title = Spring Boot - JavaFX 3 | 4 | # JMX нам не нужен, а его отключение позволит ускорить запуск 5 | spring.jmx.enabled=false 6 | 7 | # Настройка БД 8 | spring.datasource.test-on-borrow=true 9 | spring.datasource.validation-query=SELECT 1 10 | spring.jpa.show-sql=true 11 | spring.jpa.hibernate.ddl-auto=create -------------------------------------------------------------------------------- /src/main/resources/fxml/main.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |