Невозможно вызвать «com.example.service.PersonService.save(Object)», поскольку «this.personService» имеет значение null

У меня есть личная программа, использующая 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)

Можете ли вы добавить к своему вопросу всю трассировку стека ошибки, пожалуйста? когда компонент не запускается, это обычно происходит по какой-то другой причине, и трассировка стека обычно показывает это, если вы читаете его снизу вверх.

Jorge Campos 19.07.2024 19:57

Я бы предложил добавить его к вашему вопросу, отредактировать и создать для него новый форматированный раздел. Кроме того, добавленный вами бит - это не вся трассировка стека, их должно быть намного больше.

Jorge Campos 19.07.2024 20:04

ааа, я вижу, ты удалил комментарий :)

Jorge Campos 19.07.2024 20:05

@JorgeCampos - Я только что понял, что ты сказал! Мне жаль. Я отредактировал вопрос, а затем добавил его.

Kameron Hazelwood 19.07.2024 20:05

После нулевого указателя на PersonService должно быть больше этого значения. Похоже, тебе не хватает большего

Jorge Campos 19.07.2024 20:10

@JorgeCampos - я обновил его и опубликовал всю ветку, а не только ветку ошибок.

Kameron Hazelwood 19.07.2024 20:18

Очень странно, что на этом исключение заканчивается... можете ли вы отредактировать его и добавить весь контроллер или хотя бы определение класса? Правильно ли оно аннотировано?

Jorge Campos 19.07.2024 20:32

@JorgeCampos — конечно, можно! Я внес эти изменения, а также добавил их на страницу fxml.

Kameron Hazelwood 19.07.2024 20:38

Вы опубликовали Person.fxml, но опубликовали только код, который загружает PersonsRecord.fxml. Опубликуйте код, который загружает FXML, который вы разместили в вопросе.

James_D 19.07.2024 20:46

Возможно, не по теме, но я бы настоятельно рекомендовал не использовать Lombok с JavaFX. Он предполагает конкретные реализации инкапсуляции, противоречащие шаблонам, обычно используемым JavaFX.

James_D 19.07.2024 20:48

Я сделал эти обновления. Я забыл, что у меня есть два файла fxml для этой программы. Хорошо, тогда я могу обновить это.

Kameron Hazelwood 19.07.2024 20:50

Есть ли шанс опубликовать это в репозитории, где мы сможем его скачать и протестировать? Ничто не привлекает моего внимания в ваших конфигурациях, поэтому, возможно, придется запустить их, чтобы проверить.

Jorge Campos 19.07.2024 20:55

Не публикуйте код на внешнем сайте (это противоречит правилам сайта по уважительной причине). Создайте упрощенную версию своего проекта, воспроизводящую проблему, и опубликуйте ее полностью здесь. Создайте макет репозитория (или просто сервиса) с помощью простого класса, генерирующего жестко закодированные данные.

James_D 19.07.2024 21:00
repo.findAll() должен вернуть список.
David Weber 21.07.2024 09:00

Я на 99% уверен, что ваш Service равен нулю, потому что ваш controller не является Spring component. Вы отметили его @Component, и это хорошая идея, но я готов поспорить, что ваш контроллер создан не Spring Boot, а FxmlLoader. Это приводит к «игнорированию» аннотации, поскольку в new Controller() есть FxmlLoader.

David Weber 21.07.2024 09:08

Мои примеры репозиториев, демонстрирующие использование Spring Boot и JavaFx с Maven и Gradle: github.com/davidweber411/javafx-JavaFxSpringBootGradleApp github.com/davidweber411/javafx-JavaFxSpringBootMavenApp Буду признателен за бесплатную поддержку Открытый исходный код.

David Weber 21.07.2024 09:10

@DavidWeber - Спасибо за это объяснение, пример и информацию о том, куда я могу двигаться дальше. Серьезно, я ценю все это. У меня есть последний вопрос по поводу возникшей сегодня утром ошибки, которую я не могу устранить. Я скачал ваш шаблон и изменил его так, как мне нужно. Я отредактирую свой вопрос с новой ошибкой. Спасибо.

Kameron Hazelwood 22.07.2024 19:42

@jewelsea - Ах, спасибо. Я обновил сообщение об ошибке. Вчера я некоторое время смотрел на него, и applicationContext имеет значение null. Я обновил ошибку в трассировке

Kameron Hazelwood 24.07.2024 18:19

@jewelsea Хорошо, да, это определенно позволило этому сработать. Спасибо, что заметили это. Мне следовало уделить больше внимания, прежде чем публиковать эти вопросы. Если вы не возражаете, у меня последний вопрос. Каждый раз, когда необходимо загрузить новую страницу fxml, нужно ли повторно инициализировать applicationContext?

Kameron Hazelwood 24.07.2024 21:04

Объединенные комментарии в ответ. Если вам нужны дополнительные вопросы, рассмотрите возможность задать новые вопросы.

jewelsea 24.07.2024 21:56
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
1
20
126
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Объяснение:

Я совершенно уверен, что ваш сервис нулевой, потому что ваш контроллер не Spring Component. Вы отметили его @Component, и это хорошая идея, но поскольку вы используете FXML, я уверен, что ваш контроллер создан не Spring Boot, а FxmlLoader. Это приводит к «игнорированию» аннотации, поскольку FxmlLoader создает новый экземпляр вашего контроллера, используя ключевое слово new.

Примеры моих репозиториев, которые демонстрируют использование Spring Boot и JavaFx с Maven и Gradle.

JFX с Spring Boot и Gradle (лучше и актуальнее)

JFX с Spring Boot и Maven

Что делать дальше:

Отсюда у вас есть несколько вариантов.

  1. Самый простой: скачайте шаблон и измените его под свои нужды. Все предварительно настроено и работает «из коробки».
  2. Средний обходной путь: назначьте Spring Boot Application context статической переменной и получите свой сервис в компоненте, отличном от Spring, с помощью Service service = springContext.getBean(Service.class).
  3. Самое сложное: реструктурируйте свой код так, чтобы ваш контроллер стал компонентом Spring (возможно, создайте свой собственный SpringFxmlLoader класс).

Дополнительная информация:

Будьте осторожны при использовании компонентов Spring. Они могут быть без гражданства или с состоянием. Проще говоря: они могут быть синглтоном (синглтон области действия) или собственным экземпляром для каждой инъекции (прототип области действия).

Спасибо за это объяснение, пример и информацию о том, куда я могу двигаться дальше. Серьезно, я ценю все это. У меня есть последний вопрос по поводу возникшей сегодня утром ошибки, которую я не могу устранить. Я скачал ваш шаблон и изменил его так, как мне нужно. Я отредактирую свой вопрос с новой ошибкой. Спасибо.

Kameron Hazelwood 22.07.2024 17:56

В классе FxApplicationStageIsReadyListener снова преобразуйте метод JfxSpringBootStageIsReadyListener обратно в конструктор. Вы изменили его на метод void.

David Weber 25.07.2024 07:31

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

Что касается сканирования компонентов Spring

Ваш контроллер в упаковке 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 экземпляр контроллера.

Контроллер должен быть прототипом, как в этом ответе:

Тогда новые экземпляры контроллера создаются каждый раз, когда он вам нужен, вместо использования одноэлементного контроллера.

Хорошая информация! Я немного обновил репозиторий Git!

David Weber 25.07.2024 07:27

Спасибо за это объяснение. Я очень ценю ваше время, потраченное на эту проблему, за то, что вы помогли мне понять интеграцию Springboot и Javafx и настроить внедрение зависимостей. Я получаю отдельную ошибку, для которой создам новый вопрос. Серьезно еще раз спасибо за всю эту помощь.

Kameron Hazelwood 25.07.2024 20:01

Другие вопросы по теме