What is Dependency Injection
Dependency injection (DI) factors out the management of objects to a container. The container can be something as involved as a Java EE app server (ex, JBoss), a library like Google Guice or Picocontainer, or something in between like the Spring Framework. The benefit it gives to developers starts to take hold in larger programs where object reuse, sharing, and re-configuration patterns start to emerge. As a quick example, member variable defined in a class
MenuBarDelegate mbDelegate = new MenuBarDelegate();
Becomes
@Inject
MenuBarDelegate mbDelegate;
Swapping "new" for @Inject isn't a huge savings unless you look at what it takes to build a MenuBarDelegate. MenuBarDelegate may collaborate with other objects -- more "new" operators for the non-DI case -- and so the creating of the MenuBarDelegate may require the creation of an entire object graph.
The non-DI approach holds up until you need to hack into that object graph. Swapping out implementations...using a subtree of the object graph in a different place...these are all tasks that require you to repeat the object creation throughout the code. This leads to the Factory pattern and intolerable object coupling (try unit testing a Java class with a bunch of object creation time bombs in the middle of the code!).
I've personally seen larger projects (over 100 Java developers) suffer greatly without DI because everyone comes up with their own standards for building components. All the projects I work on in my consulting practice use DI: Spring or Java EE if available, Google Guice in other cases.
Meaning for JavaFX
JavaFX is the new desktop for Java. Using SceneBuilder, developers construct a UI that's linked to Controllers using @FXML annotations. What the JavaFX Controller does isn't specified. A likely use case will be to interact with some type of Service or Data Access code. This is where DI can help.
DI lets you inject objects into your JavaFX controller such that fully-created objects like Services and DAOs live alongside the JavaFX controls. See the following snippet for a typical declaration. Note that all of the objects are ready-to-use (at least in the @FXML initialize() method) and the class is completely decoupled from the @Inject objects which can be mocked in a unit test or provided another implementation as in the following paragraph.
@FXML
Button tbScan;
@FXML
Button tbUpdate;
@Inject
ErrorLogDelegate errorLogDelegate;
@Inject
AlertController alertController;
@Inject
FavoritesDAO favoritesDAO;
Use Case
Consider a Data Access Object called FavoritesDAO. In a JNLP deployment, this will be based on calls to PersistenceService for working with muffins (an Oracle name for desktop "cookies"). However, this service isn't available in Eclipse when I run the app from the command line. I want to be able to support both deployments: the target platform and my iterative code-test-debug cycle in Eclipse.
Without DI, you might use a software switch to pick between two implementations, passing the selected implementation to another object. Something like
FavoritesDAO dao = null;
if( runningAsJNLP() ) {
dao = new FavoritesJNLPPersistenceServiceDAO();
} else {
dao = new FavoritesMemoryDAO();
}
mainViewController.favoritesDAO = dao;
This may not be a big problem for a single use, but this doesn't allow for the other users of favoritesDAO or other DAOs that need to be wired up. Passing "runningAsJNLP" down the line to other classes is the kind of killer that I was describing earlier. This would be required if I expected each class to manage their own dependencies. I vastly prefer to get the environment out of the way at initialization so that my Controllers focus on business logic and UI commands.
Google Guice
I've been using Google Guice on several projects lately ranging from desktop apps to custom extensions to Talend Open Studio. It's a 480k JAR file and only takes a few lines of code to add to your app. Once configured, Guice uses standard Java annotations for DI in case you want to switch from it. A small footprint, no vendor-lock in the source code, and a few lines added to your FXMLLoaders is all it takes to retrofit your app.
Add this to your pom.xml or download Guice separately if you're not using Maven.
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
Google Guice development starts with Module definitions. If you're wiring up a bunch of classes, you can even use empty Module definitions. I'm juggling a pair of modules, one for JNLP and one of standalone. This is the software switch presented in the preceding section.
AbstractModule module = null;
if( runningAsJNLP() ) {
module = new MPUJNLPModule();
} else {
module = new MPUStandaloneModule();
}
Each module is defined in a class that extends AbstractModule.
public class MPUJNLPModule extends AbstractModule {
@Override
protected void configure() {
bind(FavoritesDAO.class).to(FavoritesJNLPPersistenceServiceDAO.class).asEagerSingleton();
}
}
And for standalone mode
public class MPUStandaloneModule extends AbstractModule {
@Override
protected void configure() {
bind(FavoritesDAO.class).to(FavoritesMemoryDAO.class).asEagerSingleton();
}
}
What these classes do is swap in a stateless Singleton object every time the source code contains "@Inject FavoritesDAO".
@Inject
FavoritesDAO favoritesDAO;
Your class declares FavoritesDAO without regard to which DAO it will actually use.
JavaFX ControllerFactory
The AbstractModule creation is followed up with the creation of a Guice Injector. The Injector is a factory for object graphs. Here's where the power of Guice comes in. A Guice Injector returns an object and its hierarchy of dependencies. So, each object returned is ready-to-go.
final Injector injector = Guice.createInjector(module);
BuilderFactory builderFactory = new JavaFXBuilderFactory();
Callback<Class<?>, Object> guiceControllerFactory = new Callback<Class<?>, Object>() {
@Override
public Object call(Class<?> clazz) {
return injector.getInstance(clazz);
}
};
Finally, some code to apply the Guice ControllerFactory.
FXMLLoader mainViewLoader= new FXMLLoader(getClass().getResource("mavenpomupdater.fxml"), null, builderFactory, guiceControllerFactory);
Parent mainView = (Parent)mainViewLoader.load();
Summary
Not only is mainView wired up with business logic, but its dependent objects are as well. That means that FavoritesDAO is eligible to be used in any class. If a later requirement calls for FavoritesDAO functionality in another part of the app, I can do the same @Inject and start working with business logic where it's needed without tearing apart a bunch of class definitions, call signatures, or factories to move the object.Caveats
There are a few wrinkles in this dependency injection involving constructors and inter-Controller references (two Controllers from different FXMLLoaders). Several Oracle tutorials promote a style of JavaFX Controller that loads FXML from within the constructor. What's presented here draws the FXMLLoaders into a centralized main. This may help with scalability, but if you're working with FXMLLoaders in your constructors you may run into a conflict because Google Guice should be able to create objects as simply as possible without interference from the other framework.
Regarding inter-Controller references, I'm using a manual wiring to connect related Controllers.
FXMLLoader alertViewLoader = new FXMLLoader(getClass().getResource("alert.fxml"), null, builderFactory, guiceControllerFactory);
Parent alertView = (Parent)alertViewLoader.load();
final AlertController alertController = alertViewLoader.getController();
mainViewController.alertController = alertController;
alertController.mainViewControllerRef = new WeakReference<MainViewController>(mainViewController);
This is because if you @Inject one Controller into another, you'll get a new object via Google Guice. However, this won't be a correctly initialized instance because JavaFX won't have set its @FXML members. You can @Inject @Singleton Controllers and I've worked on a project that used a custom @WindowScoped scope. But the default prototype (new instance) scope will not automatically hookup two Controllers from two different FXMLLoaders.
If you're starting out with a new Java FX app and if there are a lot of developers involved, consider adding Dependency Injection to your design. This will provide a standard and centralized manner of accessing business logic throughout your JavaFX Controllers. As additional patterns develop -- say Delegates to reduce the complexity of the Controllers -- all of the business logic can be reused with minor @Inject declarations.
...plus the integration of Google Guice is only 10 lines of code.
UPDATE - Feb 14, 2015
Here's a cool Java 8 Lambda that shrinks my simple Guice Controller Factory callback down to a single line.
Callback<Class<?>, Object> gcf = clazz -> injector.getInstance(clazz);
Callback is a candidate for a Lambda rewrite because it has only a single method "call". "call" takes a single generic parameter (Class<?> in my case) and returns a generic value (Object).
Pretty neat!
Thanks, that was a very useful tutorial!
ReplyDeleteYou write, that you do use this for Standalone and JNLP applications.
ReplyDeleteHow do you add this before defining the "Application"? could you provide the code for this; probably including the code for generating the JNLP (do you do this using gradle?)
I'm new to JavaFX and lost where to start at when it comes to JNLP and creating the injection stuff.
my very basic JavaFX application looks like this:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX AppStractFX");
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
The dependency injection isn't related to the JNLP, so let's get you're main working with a JNLP file.
DeletePut the Main.class file in a JAR. Sign the JAR using a code signing certficate.
I use Maven to create the JAR and a use a MANIFEST.MF configuration like this.
Permissions: all-permissions
Upload the signed JAR and a JNLP file to a web server.
Here are some sample JNLPs on the Web. You can run these apps and go into the Java Control Panel, select Temporary Internet Files, right-click on the app's entry, and select "Show JNLP file".
This example is a sandboxed app. Java will present a message to the user indicating that this app runs with a limited capability designed to protect the user. In addition to the JNLP, the sandboxed app needs an entry in the JARs META-INF/MANIFEST.MF file.
https://www.bekwam.net/background-improved/background.jnlp
This example is not sandboxed (full access requested). It's a code signing app that will sign a directory of third-party JARs.
https://www.bekwam.com/resignator/resignator.jnlp
The source for this app is on GitHub: https://github.com/bekwam/resignator-repos-1/tree/master/resignator.
Good luck
The code for the sandboxed app is also on GitHub.
Deletehttps://github.com/bekwam/examples-javafx-repos1/tree/master/examples-javafx-parent/examples-javafx-background/src/main/java/com/bekwam/examples/javafx/background
thanks for your fast reply, will have a look asap! :D
Delete