Java Spring Bean создается и добавляется в контекст, но не @Autowired

У меня есть приложение JavaFX с Spring. У меня есть компонент в классе конфигурации, который правильно добавляется в SpringContext и может быть получен вручную, но он не подключается автоматически к классу с аннотацией @Component. Я суммировал код ниже.

@SpringBootApplication
public class BoothApplication extends javafx.application.Application {

    // this is temporarily static for debug purposes
    public static ConfigurableApplicationContext springContext;
    private Parent rootNode;

    private Scene threeCardScene, debugScene, ezzieScene;

    @Autowired
    private StateMachineConfiguration stateMachineSupplier;

    @Autowired
    private GameModel gameModel;

    @Override
    public void start(Stage prinaryStage) throws IOException {
        
        <snip>
        mainStage.show();
    }

    public static void main(String[] args) {
        launch();
    }

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(BoothApplication.class);
        FXMLLoader fxmlLoader = new FXMLLoader(this.getClass().getResource("/org/overworld/tarotbooth/ezzie.fxml"));
        fxmlLoader.setControllerFactory(springContext::getBean);
        rootNode = fxmlLoader.load();
    }
    
    @Override
    public void stop() throws Exception {
        springContext.close();
    }
}

У меня есть bean, который правильно инициализирован:

@Configuration
public class StateMachineConfiguration {

    @Bean
    public StateMachine<State, Trigger> stateMachine() {

        StateMachineConfig<State, Trigger> config = new StateMachineConfig<State, Trigger>();

        /* @formatter:off */
        
        config.configure(State.IDLE)
            .permit(Trigger.APPROACH_SENSOR, State.CURIOUS)
            .permit(Trigger.PRESENCE_SENSOR, State.ENGAGED)
            .onEntry(StateMachineConfiguration::idle)
            .ignore(Trigger.PAST_READ)
            .ignore(Trigger.PRESENT_READ)
            .ignore(Trigger.FUTURE_READ)
            .ignore(Trigger.PRINTER_ERROR)
            .ignore(Trigger.TIMEOUT)
            .ignore(Trigger.ADVANCE)
            .ignore(Trigger.BAD_PLACEMENT);
    
        <snip>
                    
        /* @formatter:on */

        // config.generateDotFileInto(System.out, true);
        StateMachine<State, Trigger> stateMachine = new StateMachine<State, Trigger>(State.IDLE, config);
        stateMachine.fireInitialTransition();
        return stateMachine;
    }

    private static void idle() {
        System.out.println("idle");
    }
}

Я могу получить доступ к компоненту напрямую через SpringContext.getBean, но он не подключается автоматически:

@Component
public class DebugController implements javafx.fxml.Initializable {
    
    @Autowired
    private StateMachine<State, Trigger> stateMachine;

    @Autowired
    private GameModel gameModel; // this is also null
    
    @Autowired
    private Deck deck; // this is also null

    @FXML
    private ToggleButton approachToggle;
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        
        // this way works
        stateMachine = BoothApplication.springContext.getBean(StateMachine.class);
        
        //this way doesn't, statemachine is null
        approachToggle.setOnAction(e -> stateMachine.fire(Trigger.APPROACH_SENSOR));

    }
}

Вся иерархия пакетов находится в пакете, в котором находится @SpringBootApplication, и ее нельзя пропустить при сканировании компонентов. Как будто DebugController не внедряется. Я проверил весь импорт на предмет самозванцев с таким же именем. Я сомневаюсь, что это что-то связано с дженериками, поскольку другие поля с автоматическим подключением тоже имеют значение null.

Настройте журналы приложений для отслеживания или отладки и проверьте журналы инициализации.

Jorge Campos 11.06.2024 02:02

@JorgeCampos Спасибо. Это довольно подробный вывод, но в нем нет ничего примечательного. Я вижу, что DebugController уничтожается при завершении работы и есть ссылка на аннотации No \@EventListener, но в выводе отладки или трассировки нет ничего похожего на ошибку.

spl 11.06.2024 02:22

Так что, возможно, проблема в том, что вы изначально пытаетесь инициализировать @Autowired private StateMachineConfiguration в файле запуска приложения... в этот момент контекст Spring еще не был инициализирован, следовательно, для него задано значение null. Оно тебе там не нужно. Spring загрузит все конфигурации перед инициализацией приложения, то же самое и для GameModel. Попробуйте удалить аннотированные атрибуты Autowired из запускаемого приложения и посмотрите, что произойдет.

Jorge Campos 11.06.2024 03:35

для завершения статический контекст работает, потому что в этот момент в него загружается контекст.

Jorge Campos 11.06.2024 03:36
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте создать конструктор для DebugController, bean-компонент StateMachine будет внедрен во время создания объекта и, следовательно, перед инициализацией метода (URL url, ResourceBundle rb).

    private final StateMachine<State, Trigger> stateMachine;

    @Autowired
    public DebugController(StateMachine<State, Trigger> stateMachine) {
        this.stateMachine = stateMachine;
    }

Хорошая попытка, но она тоже не работает, однако эта попытка привела меня к фактическому решению, поэтому спасибо! Я уже знал, что инициализация() вызывается после afterPropertiesSet() Spring, поэтому я вроде как знал, что это не упорядочивание.

spl 11.06.2024 11:35
Ответ принят как подходящий

Ладно, после ночного сна я понял. Когда я представил внедрение конструктора Spring в DebugController, я получил ошибку об отсутствующем конструкторе без аргументов, и из трассировки стека я понял, что DebugController создается JavaFX, а не Spring, таким образом, существует Spring Bean, созданный @Component и другим классом. созданный JavaFX, в котором ничего не подключено. Я думал, что мог бы исправить это в методе init, но на самом деле я просто создал новый FXMLLoader и установил для него setControllerFactory(), но когда я создавал сцены в другом месте моего кода, я использовал новый FXMLLoader, поэтому контроллер Factory в этом новом FXMLLoader не был настроен на Spring::getBean(), поэтому Spring ничего не вводил в этот новый загрузчик.

Плохой:

threeCardScene = new Scene(
    new FXMLLoader(BoothApplication.class.getResource("threeCardSpread.fxml")).load(),
                640, 480);

Работающий:

threeCardScene = new Scene(
    FXMLLoader.load(BoothApplication.class.getResource("threeCardSpread.fxml"), null,
                null, springContext::getBean), 640, 480);

Поэтому вместо того, чтобы каждый раз создавать новый FXMLLoader, я использую статический метод класса, который принимает фабрику контроллера, и каждый раз передаю Spring::getBean. (Конечно, вы можете создать новый FXMLLoader для каждой сцены, установить фабрику контроллера, а затем вызвать для него нестатический метод загрузки, но это проще.)

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