I have a JavaFX + Spring Boot application. When I try to load new FXML with its controller it works fine, but when I load it second or third time, it gives me the same instance and does not create a new one.
My FXML loader as follows:
public <T> T loadAndGetController(String fxmlPath) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(context::getBean); // Spring now FXML Controller Factory
loader.setLocation(getClass().getResource(fxmlPath));
loader.setResources(ResourceBundle.getBundle("language/lang",
new Locale(languageController.getLanguage().getValue(), languageController.getLanguage().toString())));
loader.load();
return loader.getController();
}
Loading new FXML:
TabController tabController = (TabController) StageManager.loadAndGetController(FXMLViews.TAB.getFxmlFile());
And when I try to add it in my loaded controller list, the IDs are the same, as the hash codes, which means it is the same instance. Thus it didn't create a new one.
Maybe I'm loading my FXMLs wrong or just the Spring-Boot controller factory works weird in this case. Any suggestions?
The line
loader.setControllerFactory(context::getBean);
instructs the FXMLLoader
to get the controller from the spring application context: internally, the FXMLLoader
will now do something like
Class controllerClass = Class.forName(classNameFromFXMLFile);
Object controller = context.getBean(controllerClass);
The behavior of getBean
depends on how you configured the bean for that class, but by default it will have "singleton" scope. This means it will create one instance of the class, and return the same instance every time getBean(...)
is invoked with an argument mapping to that bean (e.g. the class of the bean).
You almost certainly want a new instance of the controller class each time you load the FXML file, so you should configure the bean to have "prototype" scope. With "prototype" scope, a new instance will be created each time.
The mechanism for defining the scope of the bean depends on how you are configuring your application context, but with annotation-based config you would do something like
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class FarTabController { /* ... */ }
With Java-based config you would do
@Configuration
public class ApplicationConfig {
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public FarTabController farTabController() {
return new FarTabController() ;
}
// ...
}
and if you are still using the old schema-based (XML) configuration, you would do (if I remember correctly)
<beans>
<bean class="my.package.FarTabController" scope="prototype" />
<!-- ... -->
</beans>