-
Notifications
You must be signed in to change notification settings - Fork 104
Dependency Injection
Dependency Injection is a design pattern that answers the question of how a class get references to instances of other classes it depends on. Instead of creating the instances by yourself (with the new
operator) or getting them from a factory or service locator, the references are passed into the class from outside, for example as a constructor parameter.
You can implement this design pattern by hand but there are also many frameworks available like CDI, Guice, EasyDI, Spring Framework and EJB.
MvvmFX supports CDI and Guice as dependency injection frameworks out of the box. Other frameworks can be plugged into mvvmFX but it's also possible to use mvvmFX without dependency injection at all.
There is an mvvmFX extension for CDI available. To add CDI support you need to add the maven dependency:
<dependency>
<groupId>de.saxsys</groupId>
<artifactId>mvvmfx-cdi</artifactId>
<version>${mvvmfx-version}</version>
</dependency>
This uses JBoss Weld as CDI implementation.
Your application class should extend from de.saxsys.mvvmfx.cdi.MvvmfxCdiApplication
.
Example:
public class Starter extends MvvmfxCdiApplication{
public static void main(String...args){
launch(args);
}
@Override
public void startMvvmfx(Stage stage){
// your code to initialize the view.
}
}
Another example can be seen at welcome-example.
There is an mvvmFX extension for Guice available. To add Guice support you need to add the maven dependency:
<dependency>
<groupId>de.saxsys</groupId>
<artifactId>mvvmfx-guice</artifactId>
<version>${mvvmfx-version}</version>
</dependency>
This extension is based on fx-guice.
Your application class should extend from de.saxsys.mvvmfx.guice.MvvmfxGuiceApplication
.
Example:
public class Starter extends MvvmfxGuiceApplication {
public static void main(final String[] args) {
launch(args);
}
@Override
public void startMvvmfx(final Stage stage) throws Exception {
// your code to initialize the view
}
}
Another example can be seen at welcome-example.
Both CDI and Guice are supporting field injection which means that dependencies are directly injected into fields of the class.
In the following example the annotation @Inject
is placed above a field of the class. A DI container that supports field injection will inject the instance of MyService
directly into this field even though it is marked as private
. One thing to keep in mind when using field injection is that you can't use the constructor for initialization logic because at the point in time when the constructor is invoked the field will not be injected yet:
public void MyViewModel implements ViewModel {
@Inject
private MyService service;
...
public MyViewModel() {
service.someMethod(); // throws NullPointerException
}
}
For this reason most DI frameworks provides some sort of live cylce methods that can be used for initialization logic. Both Guice and CDI support the annotation @PostConstruct
like this:
public void MyViewModel implements ViewModel {
@Inject
private MyService service;
...
@PostConstruct
public void postconstruct() {
service.someMethod(); // OK
}
}
The DI container will invoke the method that is annotated with @PostConstruct
after all dependencies that are managed by the DI container are injected.
However, this will not work for dependencies that are managed by mvvmFX framework, like @InjectScope
and @InjectRessourceBundle
. For the technical reasons behind this see issue #403.
To initialize dependencies from mvvmFX you should create a method with the signature public void initialize()
. This is a naming convention that already exists for JavaFX controllers.
See this example:
public void MyViewModel implements ViewModel {
@Inject
private MyService service;
@InjectScope
private MyScope scope;
...
@PostConstruct
public void postconstruct() {
service.someMethod(); // OK
scope.someMethod(); // throws NullPointerException
}
public void initialize() {
service.someMethod(); // OK
scope.someMethod(); // OK
}
}
Normally it's not needed to use @PostConstruct
anymore in ViewModels because you can do initialization in the initialize
method instead. While it's still possible to use @PostConstruct
you can't access dependencies that are injected by mvvmFX in this method.
Please note: You should not use the @PostConstruct
annotation on the public void initialize()
method. In this case the method would be called twice. If you use @PostConstruct
, don't name the method initialize()
.
To plug in other dependency injection frameworks you have to use the MvvmFX.setCustomDependencyInjector
method to set a callback of type javafx.util.Callback<Class<?>,Object>
.
This callback gets a class type as parameter and has to return an instance of the given type. Your implementation of this callback should return an instance that is created and managed by your dependency injection framework. This callback will be called when a fxml file is loaded to get an instance of the specified Code-Behind class (specified by the controller
attribute).
The integration of DI frameworks can be seen in the books-example where the the DI library EasyDI is used. Other examples for EasyDI can be found here and here.
When no dependency injection framework is used nothing has to be done. In this case the code behind classes are instantiated by the FXMLLoader.
MvvmFX is based on the usage of FXML as view description technology. In the fxml file you specify the "Code behind" part of the View with the controller
attribute.
By default this controller classes are instantiated by javafx's FXMLLoader to be able to inject view controls.
To combine this with your dependency injection framework you need to tell the FXMLLoader where it can get controller instances. Internally this is done by FXMLLoader's
setControllerFactory
method which is encapsulated by mvvmFX.
No, while dependency injection has many advantages you aren't forced to use it. MvvmFX can be used without dependency injection frameworks.
There is a special annotation @InjectViewModel
that is used to inject the ViewModel into the View. When using CDI or Guice the developer could be tempted to use @Inject
instead.
This is wrong. Always use @InjectViewModel to inject the ViewModel into the View, not @Inject.
The reason is that internally we are doing some special checks and logic before we inject the ViewModel into the View that can't be done when @Inject
is used:
- Check for the correct type of the ViewModel for a View. If the Type doesn't match a meaningful exception will be thrown.
-
ResourceBundles are only injected into the ViewModel when it is itself injected via
@InjectViewModel
. - When loading a View via
FluentViewLoader
an existing ViewModel instance can be provided (for exampleFluentViewLoader.fxmlView(MyView.class).viewModel(myViewModelInstance).load()
). This existing ViewModel instance can only be injected via@InjectViewModel
.
Even when using @InjectViewModel
the ViewModel instance is still managed by the DI framework as we are internally taking the instance from the DI. This means that inside of the ViewModel you can inject other dependencies via @Inject
like with any other class.
Another aspect that should be kept in mind is lifecycle methods: When using @InjectViewModel
, the ViewModel instance will not be available in a post-construct method (annotated with @PostConstruct
). Instead you should do your initialization logic in the initialize
method that is suggested by standard JavaFX behaviour.