У меня есть приложение 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.
@JorgeCampos Спасибо. Это довольно подробный вывод, но в нем нет ничего примечательного. Я вижу, что DebugController уничтожается при завершении работы и есть ссылка на аннотации No \@EventListener, но в выводе отладки или трассировки нет ничего похожего на ошибку.
Так что, возможно, проблема в том, что вы изначально пытаетесь инициализировать @Autowired private StateMachineConfiguration
в файле запуска приложения... в этот момент контекст Spring еще не был инициализирован, следовательно, для него задано значение null. Оно тебе там не нужно. Spring загрузит все конфигурации перед инициализацией приложения, то же самое и для GameModel. Попробуйте удалить аннотированные атрибуты Autowired из запускаемого приложения и посмотрите, что произойдет.
для завершения статический контекст работает, потому что в этот момент в него загружается контекст.
Попробуйте создать конструктор для 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, поэтому я вроде как знал, что это не упорядочивание.
Ладно, после ночного сна я понял. Когда я представил внедрение конструктора 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 для каждой сцены, установить фабрику контроллера, а затем вызвать для него нестатический метод загрузки, но это проще.)
Настройте журналы приложений для отслеживания или отладки и проверьте журналы инициализации.