0

In order to config my Spring application, I use an application.properties file, and a configuration class like so:

@Configuration
@ComponentScan(basePackages = {"org.fg.soma.manager.*"})
@PropertySource(value = "file:config/application.properties")
public class SpringConfig {
    @Autowired
    public SpringFXMLLoader springFXMLLoader;

    @Bean
    @Lazy // Create only after spring context bootstrap
    public StageManager stageManager(Stage stage) {
        return new StageManager(springFXMLLoader, stage);
    }
}

I configure here a bean for the class StageManager, which helps to switch scenes in my JavaFX application. This is a part of the actual class:

public final class StageManager {
    private final SpringFXMLLoader springFXMLLoader;
    private Stage stage;

    public StageManager(SpringFXMLLoader springFXMLLoader, Stage stage) {
        this.stage = stage;
        this.springFXMLLoader = springFXMLLoader;
    }

SpringFXMLLoader's job is to load the FXML file via Spring and not via JavaFX so it could be later used as a bean:

@Component
public class SpringFXMLLoader {
    private final ApplicationContext applicationContext;

    @Autowired
    public SpringFXMLLoader(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public Parent load(String fxmlPathName) throws IOException {
        FXMLLoader loader = new FXMLLoader();
        loader.setControllerFactory(applicationContext::getBean);
        loader.setResources(ResourceBundle.getBundle("Bundle"));
        loader.setLocation(getClass().getResource(fxmlPathName));

        return loader.load();
    }
}

Finally, I have the main Spring Boot application class which launches my JavaFX application:

@SpringBootApplication
public class ModManagerApplication extends Application {

    private ConfigurableApplicationContext springContext;

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

    @Override
    public void init() {
        String[] args = getParameters().getRaw().toArray(new String[0]);

        springContext = new SpringApplicationBuilder(ModManagerApplication.class)
                .headless(false)
                .run(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        StageManager stageManager = springContext.getBean(StageManager.class, primaryStage);
        stageManager.switchScene(ApplicationView.MOD_MANAGER);
    }

    @Override
    public void stop() {
        springContext.stop();
    }
}

When I run my application, I get the following error in the console:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.fg.soma.manager.view.StageManager' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1133)
    at org.fg.soma.manager.application.ModManagerApplication.start(ModManagerApplication.java:30)

Line 30 refers to this line: StageManager stageManager = springContext.getBean(StageManager.class, primaryStage);

I use Intellij-IDEA 2020 to configure my Spring Boot Facets like so: enter image description here

I have no idea what could cause this, so any help is appreciated.

  • What `Stage` are you expecting to be passed to the `stageManager()` method in your config? There’s no obvious candidate bean for this parameter. – James_D Jul 04 '20 at 12:20
  • I pass the `primaryStage` object that comes from JavaFX. That's why I configured it to be lazy, because I want the JavaFX initialize first with the primary stage and only then create the StageManager bean. – user10796940 Jul 04 '20 at 12:21
  • Hmm. That looks like it should work, per the docs, though I've never tried it. Try `applicationContext.getBeanFactory().registerSingleton("primaryStage", primaryStage);`, and remove the additional argument to `getBean()`. That's a little unsatisfactory, but if you can get that to work it at least narrows down where the problem is. – James_D Jul 04 '20 at 12:44
  • I think this also works if you remove the `StageManager` bean definition from the `SpringConfig` class, and annotate the `StageManager` class with `@Component @Lazy`. That's probably cleaner than the previous suggestion, though I generally prefer config classes to annotation-based config. The issue is that the additional parameters to `getBean` are passed to bean constructors (or factory methods), but not to config bean supplier methods. – James_D Jul 04 '20 at 13:27
  • I'll try to remove them from the constructor and use a set method instead – user10796940 Jul 04 '20 at 13:31
  • That will work too :). But I'm experimenting a bit and really can't figure out why your original code (which is definitely cleaner than either of my suggestions, or resorting to setter injection) isn't working. – James_D Jul 04 '20 at 13:35
  • I tried to reproduce this as closely as I could to your actual example, and it all worked. Can you expand this to a complete [mre] (complete the `StageManager` class with a `switchScene()` method, hardcode an FXML path name, add a basic FXML, the `pom.xml`, and either `module-info.java` or runtime parameters)? – James_D Jul 04 '20 at 15:41
  • Hi. I actually re-written the entire thing again without changing anything and now it works. However, I have some issues with how I access other Java FXML controllers in other controllers, as they do not initialize properly. I'm still trying to figure out if it's an issue with my FXML file or maybe something with spring. – user10796940 Jul 04 '20 at 15:47
  • Your controllers should be `prototype` scope, since you need a new controller instance each time you load a given FXML file, so injecting one controller into another won't work (because you presumably need a specific controller instance). But generally, having one controller depend on another is just really bad practice; you should be injecting a (singleton-scoped) model instance to the controllers. Assuming each controller observes the model in the usual MVC way, you effect updates in one view from another by updating the model. – James_D Jul 04 '20 at 15:50
  • I see. Can it work if I annotate the controllers with a prototype scope and then put them as a parameter in the Controller's constructor (The relevant controllers for each controller)? I'm not sure I get the idea of creating a Singleton model instance of the controller. – user10796940 Jul 04 '20 at 16:05
  • I don't think that works, because the controller instance passed to the constructor won't be associated with an FXML file (so, e.g. all its `@FXML`-annotated fields will be null). Really if you have one controller talking to another, you're doing things wrong. See https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx/32343342#32343342 for (a non-spring version of) the basic idea. – James_D Jul 04 '20 at 16:08
  • I'll look at it, thanks. – user10796940 Jul 04 '20 at 16:13

0 Answers0