├── .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 | 
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 extends AbstractJavaFxApplicationSupport> 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 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/test/java/ru/habrahabr/ApplicationTests.java:
--------------------------------------------------------------------------------
1 | package ru.habrahabr;
2 |
3 | import javafx.embed.swing.JFXPanel;
4 | import org.junit.BeforeClass;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.springframework.boot.test.SpringApplicationConfiguration;
8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
9 |
10 | @RunWith(SpringJUnit4ClassRunner.class)
11 | @SpringApplicationConfiguration(classes = Application.class)
12 | public class ApplicationTests {
13 |
14 | @BeforeClass
15 | public static void bootstrapJavaFx(){
16 | // implicitly initializes JavaFX Subsystem
17 | // see http://stackoverflow.com/questions/14025718/javafx-toolkit-not-initialized-when-trying-to-play-an-mp3-file-through-mediap
18 | new JFXPanel();
19 | }
20 |
21 | @Test
22 | public void contextLoads() {
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------