У меня есть личная программа, использующая Springboot и Javafx. Я пытался получить больше навыков в создании приложений Springboot и Javafx. Программа запускает страницу fxml, на которой человек может обновить свои записи, нажимает «Сохранить», а затем сохраняется в базе данных mssql.
Я пытаюсь сохранить новую запись о человеке в базе данных mssql.
PersonEntity.java
@Entity
@Getter
@NoArgsConstructor(force = true)
@Data
@Table(name = "persons")
public class PersonEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "personID", nullable = false)
private final Integer personID;
@Column(name = "first_name")
@Setter
private String firstName;
@Column(name = "last_name")
@Setter
private String lastName;
@Column(name = "dob")
@Setter
private String dob;
PersonRepo.java
@Repository
public interface PersonRepo extends JpaRepository<PersonEntity, String> {
List<PersonEntity> findAll();
}
PersonService.java
public interface PersonService{
Optional<PersonEntity> save(PersonEntity personEntity);
PersonEntity update(PersonEntity personEntity);
PersonEntity findAll();
}
PersonServiceImpl.java
@Service
@RequiredArgsConstructor
public class PersonServiceImplimplements PersonService {
@Autowired
private PersonRepo repo;
@Override
public Optional<PersonEntity> save(PersonEntity personEntity) {
return Optional.of(repo.save(personEntity));
}
@Override
public PersonEntity update(PersonEntity personEntity){ return repo.save(personEntity);}
@Override
public PersonEntity findAll() {
return (PersonEntity ) repo.findAll();
}
}
PersonController.java
@Component
public class PersonController {
@Autowired
PersonService personService;
public TextField lName;
public TextField fName;
public TextField dob;
public Button onSaveNewPerson;
@FXML
public void onSaveNewPerson(ActionEvent event) throws SQLException {
if (fName != null){
PersonEntity newPerson = new PersonEntity ();
fName.setText(newPerson.getFirstName());
personService.save(newPerson);
}
}
PersonsRecord.fxml
<AnchorPane fx:id = "scenePane" prefHeight = "400.0" prefWidth = "600.0" xmlns = "http://javafx.com/javafx/20.0.1" xmlns:fx = "http://javafx.com/fxml/1" fx:controller = "com.example.controllers.PersonController">
<Pane layoutX = "-2.0" layoutY = "91.0" prefHeight = "800.0" prefWidth = "1004.0">
<Label layoutX = "14.0" layoutY = "14.0" text = "New Person Form">
<font>
<Font size = "20.0"/>
</font>
</Label>
<Label layoutX = "14.0" layoutY = "69.0" text = "First Name"/>
<Label layoutX = "16.0" layoutY = "130.0" text = "Last name"/>
<Label layoutX = "18.0" layoutY = "130.0" text = "Date of Birth"/>
<Label layoutX = "20.0" layoutY = "130.0" text = "Address"/>
<TextField fx:id = "fName" layoutX = "98.0" layoutY = "66.0"/>
<TextField fx:id = "lName" layoutX = "98.0" layoutY = "126.0"/>
<TextField fx:id = "dob" layoutX = "98.0" layoutY = "146.0"/>
<TextField fx:id = "personAddress" layoutX = "98.0" layoutY = "166.0"/>
<Button fx:id = "saveNewPerson" layoutX = "803.0" layoutY = "333.0" mnemonicParsing = "false"
onAction = "#onSaveNewPerson" prefHeight = "58.0" prefWidth = "187.0" text = "Save New Person"/>
<Label layoutX = "597.0" layoutY = "70.0" text = "DOB"/>
<TextField fx:id = "dob" layoutX = "691.0" layoutY = "66.0"/>
</Pane>
</AnchorPane>
В настоящее время после нажатия кнопки onSaveNewPerson я получаю следующую ошибку:
Невозможно вызвать «com.example.services.PersonService.save(com.example.entity.persons.PersonEntity)», поскольку «this.personService» имеет значение NULL.
Я хочу сохранить все текстовые поля, но сейчас я просто тестирую поле fname.
Я убедился, что @Autowired установлен для personService в контроллере. Я также убедился, что @Repository установлен для PersonRepo.
Я также добавил инструменты CommandLineRunner в файл FxApplication и поручил тестировщику добавить в базу данных, чтобы обеспечить правильное подключение. Все прошло нормально, и человек был успешно введен. Код для расследования -
@SpringBootApplication
@EnableJpaRepositories("com.example.repo")
public class FxApplication extends Application implements CommandLineRunner {
private ConfigurableApplicationContext springContext;
private Parent root;
@Autowired
PersonRepo personRepo;
public static void main(String[] args){
launch(FxApplication.class, args);
}
@Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setControllerFactory(springContext::getBean);
fxmlLoader.setLocation(getClass().getResource("/PersonsRecord.fxml"));
Parent root = fxmlLoader.load();
stage.setTitle("Add NewPerson");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
@Override
public void init() throws Exception{
springContext = SpringApplication.run(FxApplication.class);
}
@Override
public void stop() throws Exception{
springContext.close();
Platform.exit();
}
@Override
public void run(String... args) throws Exception {
PersonEntity person = new PersonEntity ();
person.setFirstName("TestSpring3");
person.setLastName("LastNameTest4");
person.setDOB("1/1/2000")
personRepo.save(person);
}
}
Я отредактировал свой код и использовал шаблон, предоставленный @David Weber.
Теперь это мой класс FxApplicationLauncher.java, который я отредактировал.
package com.example;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.stereotype.Component;
import java.io.IOException;
@SpringBootApplication
@EnableJpaRepositories("com.example.repo")
public class FxApplicationLauncher {
public static void main(String[] args) {
Application.launch(FxApplication.class, args);
}
public static class FxApplication extends Application {
private ConfigurableApplicationContext context;
@Override
public void init(){
ApplicationContextInitializer<GenericApplicationContext> initializer = applicationContext -> {
applicationContext.registerBean(Application.class, () -> FxApplication.this);
applicationContext.registerBean(Parameters.class, this::getParameters);
applicationContext.registerBean(HostServices.class, this::getHostServices);
};
this.context = new SpringApplicationBuilder()
.sources(FxApplicationLauncher.class)
.initializers(initializer)
.run(getParameters().getRaw().toArray(new String[0]));
}
@Override
public void start(Stage stage) { this.context.publishEvent(new StageIsReadyEvent(stage)); }
@Override
public void stop() throws Exception {
context.close();
Platform.exit();
System.exit(0);
}
@Component
public static class FxApplicationStageIsReadyListener implements ApplicationListener<StageIsReadyEvent>{
@Value("${spring.application.name}")
private String applicationTitle;
private ApplicationContext applicationContext;
public void JfxSpringBootStageIsReadyListener(
@Value("${spring.application.name}") String applicationTitle,
ApplicationContext applicationContext) {
this.applicationTitle = applicationTitle;
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(StageIsReadyEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("/PersonsRecord.fxml"));
fxmlLoader.setControllerFactory(applicationContext::getBean);
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 600, 600);
Stage stage = event.getStage();
stage.setScene(scene);
stage.setTitle(this.applicationTitle);
stage.show();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static class StageIsReadyEvent extends ApplicationEvent {
public Stage getStage() {
return (Stage) getSource();
}
public StageIsReadyEvent(Stage source) {
super(source);
}
}
}
}
По какой-то причине applicationContext имеет значение null.
java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:208)
at com.example.gaitlabapp.FxApplicationLauncher$FxApplication$FxApplicationStageIsReadyListener.onApplicationEvent(FxApplicationLauncher.java:84)
at com.example.gaitlabapp.FxApplicationLauncher$FxApplication$FxApplicationStageIsReadyListener.onApplicationEvent(FxApplicationLauncher.java:65)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:451)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:384)
at com.example.gaitlabapp.FxApplicationLauncher$FxApplication.start(FxApplicationLauncher.java:51)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
at java.base/java.lang.Thread.run(Thread.java:842)
Я бы предложил добавить его к вашему вопросу, отредактировать и создать для него новый форматированный раздел. Кроме того, добавленный вами бит - это не вся трассировка стека, их должно быть намного больше.
ааа, я вижу, ты удалил комментарий :)
@JorgeCampos - Я только что понял, что ты сказал! Мне жаль. Я отредактировал вопрос, а затем добавил его.
После нулевого указателя на PersonService должно быть больше этого значения. Похоже, тебе не хватает большего
@JorgeCampos - я обновил его и опубликовал всю ветку, а не только ветку ошибок.
Очень странно, что на этом исключение заканчивается... можете ли вы отредактировать его и добавить весь контроллер или хотя бы определение класса? Правильно ли оно аннотировано?
@JorgeCampos — конечно, можно! Я внес эти изменения, а также добавил их на страницу fxml.
Вы опубликовали Person.fxml
, но опубликовали только код, который загружает PersonsRecord.fxml
. Опубликуйте код, который загружает FXML, который вы разместили в вопросе.
Возможно, не по теме, но я бы настоятельно рекомендовал не использовать Lombok с JavaFX. Он предполагает конкретные реализации инкапсуляции, противоречащие шаблонам, обычно используемым JavaFX.
Я сделал эти обновления. Я забыл, что у меня есть два файла fxml для этой программы. Хорошо, тогда я могу обновить это.
Есть ли шанс опубликовать это в репозитории, где мы сможем его скачать и протестировать? Ничто не привлекает моего внимания в ваших конфигурациях, поэтому, возможно, придется запустить их, чтобы проверить.
Не публикуйте код на внешнем сайте (это противоречит правилам сайта по уважительной причине). Создайте упрощенную версию своего проекта, воспроизводящую проблему, и опубликуйте ее полностью здесь. Создайте макет репозитория (или просто сервиса) с помощью простого класса, генерирующего жестко закодированные данные.
repo.findAll()
должен вернуть список.
Я на 99% уверен, что ваш Service
равен нулю, потому что ваш controller
не является Spring component
. Вы отметили его @Component
, и это хорошая идея, но я готов поспорить, что ваш контроллер создан не Spring Boot, а FxmlLoader
. Это приводит к «игнорированию» аннотации, поскольку в new Controller()
есть FxmlLoader
.
Мои примеры репозиториев, демонстрирующие использование Spring Boot и JavaFx с Maven и Gradle: github.com/davidweber411/javafx-JavaFxSpringBootGradleApp github.com/davidweber411/javafx-JavaFxSpringBootMavenApp Буду признателен за бесплатную поддержку Открытый исходный код.
@DavidWeber - Спасибо за это объяснение, пример и информацию о том, куда я могу двигаться дальше. Серьезно, я ценю все это. У меня есть последний вопрос по поводу возникшей сегодня утром ошибки, которую я не могу устранить. Я скачал ваш шаблон и изменил его так, как мне нужно. Я отредактирую свой вопрос с новой ошибкой. Спасибо.
@jewelsea - Ах, спасибо. Я обновил сообщение об ошибке. Вчера я некоторое время смотрел на него, и applicationContext имеет значение null. Я обновил ошибку в трассировке
@jewelsea Хорошо, да, это определенно позволило этому сработать. Спасибо, что заметили это. Мне следовало уделить больше внимания, прежде чем публиковать эти вопросы. Если вы не возражаете, у меня последний вопрос. Каждый раз, когда необходимо загрузить новую страницу fxml, нужно ли повторно инициализировать applicationContext?
Объединенные комментарии в ответ. Если вам нужны дополнительные вопросы, рассмотрите возможность задать новые вопросы.
Объяснение:
Я совершенно уверен, что ваш сервис нулевой, потому что ваш контроллер не Spring Component
. Вы отметили его @Component
, и это хорошая идея, но поскольку вы используете FXML
, я уверен, что ваш контроллер создан не Spring Boot
, а FxmlLoader
. Это приводит к «игнорированию» аннотации, поскольку FxmlLoader создает новый экземпляр вашего контроллера, используя ключевое слово new
.
Примеры моих репозиториев, которые демонстрируют использование Spring Boot и JavaFx с Maven и Gradle.
JFX с Spring Boot и Gradle (лучше и актуальнее)
Что делать дальше:
Отсюда у вас есть несколько вариантов.
Spring Boot Application context
статической переменной и получите свой сервис в компоненте, отличном от Spring, с помощью Service service = springContext.getBean(Service.class)
.SpringFxmlLoader
класс).Дополнительная информация:
Будьте осторожны при использовании компонентов Spring. Они могут быть без гражданства или с состоянием. Проще говоря: они могут быть синглтоном (синглтон области действия) или собственным экземпляром для каждой инъекции (прототип области действия).
Спасибо за это объяснение, пример и информацию о том, куда я могу двигаться дальше. Серьезно, я ценю все это. У меня есть последний вопрос по поводу возникшей сегодня утром ошибки, которую я не могу устранить. Я скачал ваш шаблон и изменил его так, как мне нужно. Я отредактирую свой вопрос с новой ошибкой. Спасибо.
В классе FxApplicationStageIsReadyListener
снова преобразуйте метод JfxSpringBootStageIsReadyListener
обратно в конструктор. Вы изменили его на метод void.
Информация здесь заменяет мои комментарии к вопросу и отвечает на дополнительные вопросы из комментариев, поэтому является дополнительным, а не прямым ответом. Первоначальные комментарии касались первоначально заданного вопроса и могут не отражать информацию, содержащуюся в обновленном вопросе.
Ваш контроллер в упаковке com.example.controllers
.
Приложение SpringBoot по умолчанию сканирует аннотированные компоненты в родственных и дочерних пакетах. Если вашего класса FXApplication
нет в com.example
, то com.example.controllers
не является дочерним, и контроллер не будет сканироваться и обрабатываться как аннотированный Spring bean-компонент для внедрения зависимостей.
Когда ты бежишь
launch(FxApplication.class, args);
JavaFX создаст экземпляр FxApplication.class.
Когда вы запустите этот код, среда Spring запустится после создания экземпляра FxApplication.class
, возвращающего контекст Spring, который был инициализирован при запуске.
springContext = SpringApplication.run(FxApplication.class);
springContext
будет установлен на экземпляре, созданном JavaFX, а не на экземпляре, созданном Spring. Автоматическое подключение personRepo
в FXApplication
будет установлено в экземпляре, созданном Spring, но не в экземпляре, созданном JavaFX.
Чтобы избежать путаницы в отношении экземпляров приложений Spring и JavaFX, я рекомендую использовать для каждого из них отдельные классы, как описано в:
сегодня утром я получил ошибку, которую не могу устранить. Я скачал ваш шаблон и изменил его так, как мне нужно. Я отредактирую свой вопрос с новой ошибкой.
«Исключение в методе запуска приложения» регистрируется кодом платформы JavaFX.
В вашем методе start
оберните код в try/catch, чтобы узнать, что вызывает исключение:
try {
/** your code **/
} (catch Throwable t) {
t.printStackTrace();
}
Обновите свой вопрос, включив в него записанную трассировку стека.
Ваш код отличается от кода Дэвида. Ваш класс называется FxApplicationStageIsReadyListener
, поэтому конструктор должен иметь то же имя. Вместо этого вы сделали конструктор обычным методом и присвоили ему возвращаемый тип void
-> он никогда не будет вызываться, и его поля (включая public void JfxSpringBootStageIsReadyListener
) никогда не будут инициализированы.
Каждый раз, когда необходимо загрузить новую страницу fxml, нужно ли инициализировать applicationContext?
Нет.
Контекст приложения будет действовать как Spring Bean с одноэлементной областью действия. Он только один, и он инициализируется только один раз при запуске приложения Spring. Если контекст приложения не может быть инициализирован Spring, Spring не запустится.
Последующие операции с контекстом обновят внутреннее состояние существующего экземпляра. Пока у вас есть ссылка на экземпляр контекста, который вы получили после запуска приложения, он уже будет инициализирован и обновлен.
Вы используете контекст для создания и возврата компонента-контроллера:
fxmlLoader.setControllerFactory(applicationContext::getBean);
Компонент-контроллер — это созданный и инициализированный Spring экземпляр контроллера.
Контроллер должен быть прототипом, как в этом ответе:
Добавление внедрения зависимостей Spring в JavaFX (репозиторий JPA, сервис)
@Component
@Scope("prototype")
public class DemoController {
// ...
}
Тогда новые экземпляры контроллера создаются каждый раз, когда он вам нужен, вместо использования одноэлементного контроллера.
Хорошая информация! Я немного обновил репозиторий Git!
Спасибо за это объяснение. Я очень ценю ваше время, потраченное на эту проблему, за то, что вы помогли мне понять интеграцию Springboot и Javafx и настроить внедрение зависимостей. Я получаю отдельную ошибку, для которой создам новый вопрос. Серьезно еще раз спасибо за всю эту помощь.
Можете ли вы добавить к своему вопросу всю трассировку стека ошибки, пожалуйста? когда компонент не запускается, это обычно происходит по какой-то другой причине, и трассировка стека обычно показывает это, если вы читаете его снизу вверх.