Since JavaFX runtime wants to instantiate my Application object and all of my controller objects, how do I inject dependencies into these objects?
If objects were instantiated by a DI framework, like Spring, the framework would wire up all the dependencies. If I was instantiating the objects manually, I would provide the dependencies through constructor parameters. But what do I do in a JavaFX application?
Thanks!
You don't have to use FXML or SceneBuilder. You can simply create the objects yourself and add them to your Scene/Stage yourself. It's entirely open as to how you implement it. I've implemented a Screen Management library where it handles either FXML or manually created screens.
JavaFX is available on the leading desktop operating systems (Windows, Linux, and Mac OS X). Although it has gone through some painful changes, its evolution proves its vendor's level of commitment. As the successor to Swing, it is being used by an increasing number of Java developers.
JavaFX enables you to design with Model-View-Controller (MVC), through the use of FXML and Java. The "Model" consists of application-specific domain objects, the "View" consists of FXML, and the "Controller" is Java code that defines the GUI's behavior for interacting with the user.
The main() method is not required for JavaFX applications when the JAR file for the application is created with the JavaFX Packager tool, which embeds the JavaFX Launcher in the JAR file.
You can specify a controller factory for the FXMLLoader
. The controller factory is a function that maps the controller class to an object (presumably, but not necessarily, an instance of that class) which will be used as the controller.
So if you want Spring to create the controller instances for you, this can be as simple as:
ApplicationContext context = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
loader.setControllerFactory(context::getBean);
Parent root = loader.load();
SomeController controller = loader.getController(); // if you need it...
// ...
And now the FXMLLoader
will create controller instances for a Class<?> c
by calling context.getBean(c);
.
So, e.g., you could have a configuration:
@Configuration
public class AppConfig {
@Bean
public MyService service() {
return new MyServiceImpl();
}
@Bean
@Scope("prototype")
public SomeController someController() {
return new SomeController();
}
// ...
}
with
public class SomeController {
// injected by FXMLLoader:
@FXML
private TextField someTextField ;
// Injected by Spring:
@Inject
private MyService service ;
public void initialize() {
someTextField.setText(service.getSomeText());
}
// event handler:
@FXML
private void performAction(ActionEvent e) {
service.doAction(...);
}
}
If you're not using a DI framework, and you want to do the injection "by hand", you can do so, but it involves using quite a lot of reflection. The following shows how (and will give you an idea of how much ugly work Spring is doing for you!):
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
MyService service = new MyServiceImpl();
loader.setControllerFactory((Class<?> type -> {
try {
// look for constructor taking MyService as a parameter
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1) {
if (c.getParameterTypes()[0]==MyService.class) {
return c.newInstance(service);
}
}
}
// didn't find appropriate constructor, just use default constructor:
return type.newInstance();
} catch (Exception exc) {
throw new RuntimeException(exc);
}
});
Parent root = loader.load();
// ...
and then just do
public class SomeController {
private final MyService service ;
public SomeController(MyService service) {
this.service = service ;
}
// injected by FXMLLoader:
@FXML
private TextField someTextField ;
public void initialize() {
someTextField.setText(service.getSomeText());
}
// event handler:
@FXML
private void performAction(ActionEvent e) {
service.doAction(...);
}
}
Finally, you might want to check out afterburner.fx, which is a very lightweight (in all the best ways) JavaFX-specific DI framework. (It uses a convention-over-configuration approach, where you just match FXML file names to controller class names, and optionally CSS file names, and everything just works.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With