I have problems with combining JavaFX and Spring. I have simple JavaFX application, which works fine. Now I am trying to add some Spring to it. I followed JavaFX 2 with Spri
The FXMLLoader.load(URL, ResourceBundle) method you are calling is a static
method - so it actually pays no attention to the FXMLLoader
instance you instantiated, and consequently ignores the controllerFactory
which references your Spring bean factory.
Rewrite your SpringFXMLLoader
class as follows:
public class SpringFxmlLoader {
private static final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringApplicationConfig.class);
public Object load(String url, String resources) {
FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(clazz -> applicationContext.getBean(clazz));
loader.setLocation(getClass().getResource(url));
loader.setResources(ResourceBundle.getBundle(resources));
try {
return loader.load();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
This uses the instance method loader.load() which will use your controller factory: i.e. it will use Spring to instantiate the controller.
The reason you see the controller loaded twice is that by default the bean factory gives the controller singleton scope and makes it eagerly created, so as soon as you create the bean factory (applicationContext
) it creates a controller. That controller will have its dataProvider
initialized (but only after the constructor has completed, of course). Then the call to the static FXMLLoader.load(...)
method creates a second controller by the usual mechanism (i.e. by calling its no-arg constructor). That instance will not have its dataProvider
initialized at any time.
As an aside, you probably don't want controllers to be singletons. If you were to load your FXML file twice, to get two instances of the Parent
, you would likely need each instance to have its own controller, otherwise strange behavior would ensue. I would recommend making the controller a prototype (which means the bean factory will create a new instance every time one is requested, instead of reusing a single instance). You can do this with the following in your config class:
@Configuration
@ComponentScan(basePackages = {"mycompany.imageviewer.controller", "mycompany.imageviewer.dataprovider.impl" })
public class SpringApplicationConfig {
private static final Logger LOG = Logger.getLogger(SpringApplicationConfig.class);
@Bean
public DataProvider dataProvider() {
LOG.debug("Initializing dataProvider via SpringApplicationConfig");
return new DataProviderImpl();
}
@Bean
@Scope("prototype")
public ImageViewerController imageViewerController() {
LOG.debug("Initializing ImageViewerController via SpringApplicationConfig");
return new ImageViewerController();
}
}