- JavaFX 2.0, FXML and Spring
- Better Controller Injection
- Multiple Controllers with Shared Resources
- Views within Views, Controllers within Controllers
- Generic Controllers
- Client Server with JavaFX 2 and Hessian (+Guice +FXML)
- JFX Flow early access
- JavaFX and MVP – a smörgåsbord of design patterns
- JavaFX and Maven
- Porting “First Contact” to Spring
- Going Remote – JavaFX and Spring Remoting
- JavaFX and persistence: adding database support
- Search like you mean it
- Captains Log
- JavaFX and Spring Security
There are two things about our controller injection in our last post that I’m not entirely happy with. The first is that the SampleApp is the one responsible for loading the FXML file and wiring it up to the controller. We have a factory class for our UI (SampleAppFactory) so ideally all the work of creating and wiring up all the GUI elements should be done in here.
The second s that Richard’s post came from his earlier exploration of future directions for FXML and the discussions around that. Some of these features are not available yet, so Richard has found some creative ways to demo the concepts using scripting and the namespace. We probably don’t want to be using this style of coding for commercial apps so until the next release of FXML we need to work with what we’ve got – i.e. we need to have an explicitly named controller class in the FXML and let the loader instantiate our class and do our bindings. This has some drawbacks but using Spring (or Guice’s) annotation based configuration, we can make it work well enough.
For those that want to skip the details and just see the code: http://code.google.com/p/jfxee/source/browse/trunk/jfxspring2
So let’s first revert to the standard way of loading controllers in FXML and ditch the magical namespace and scripting. This standard way is well documented in the official FXML guide: http://download.oracle.com/javafx/2.0/api/javafx/fxml/doc-files/introduction_to_fxml.html
Our controller is now going to be defined and created in the FXML file, but using Spring’s (or Guice’s) annotation based injection we can still inject all the dependencies (so long as we use field injection and not constructor injection). One challenge though will be that both our controller and our view need to be available through the factory. The controller needs to be exposed in order to get the Person bean injected into it, but our view needs to be exposed so that it can be added to the scene. The complication is that the FXML loader creates both in a single call, so we need to get creative.
There are a lot of ways to solve this problem, but the one that works best both now and for future benefits is to give the controller a reference to its view. Anyone wanting the view, can then just access the controller from the factory and retrieve the view from it.
So if we revert our controller back to the more traditional form, giving it access to its view is a simple case of binding the root node of the FXML to a variable in the controller. To keep life simple we’re not going to bother including the Person name on the button for now – we’ll add this back in later, it just confuses things at this stage. Here’s how it looks:
SampleController.java
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import org.springframework.beans.factory.annotation.Autowired;
public class SampleController
{
@FXML private Node view;
@Autowired private Person person;
public Node getView()
{
return view;
}
public Person getPerson()
{
return person;
}
public void print(ActionEvent event)
{
System.out.println("Well done, " + person.getFirstName() + "!");
}
}
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?language javascript?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<StackPane fx:id="view"
fx:controller="com.zenjava.jfxspring.SampleController"
xmlns:fx="http://javafx.com/fxml">
<children>
<Button text="Click Me" fx:id="printBtn" onAction="#print" />
</children>
</StackPane>
Great, we now have a nice, simple, traditional controller. The whole point of this however was to get dependency injection working. Haven’t we lost this now that the controller is being created by the FXMLLoader? Not quite, luckily with Spring’s annotation based configuration we are free to create the controller anyway we want, the injected properties are set only when we return the controller from a factory method marked with @Bean. Let’s update our factory then to use the new controller. At the same time we will also remove the FXML loading from the SampleApp class and move it inside the factory – this was the second problem we wanted to solve.
Here’s how our factory now looks:
import javafx.fxml.FXMLLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.io.InputStream;
@Configuration
public class SampleAppFactory
{
@Bean
public Person person()
{
return new Person("Richard");
}
@Bean
public SampleController sampleController() throws IOException
{
return (SampleController) loadController("/sample.fxml");
}
protected Object loadController(String url) throws IOException
{
InputStream fxmlStream = null;
try
{
fxmlStream = getClass().getResourceAsStream(url);
FXMLLoader loader = new FXMLLoader();
loader.load(fxmlStream);
return loader.getController();
}
finally
{
if (fxmlStream != null)
{
fxmlStream.close();
}
}
}
}
And here’s the start method for our SampleApp class. We’ve been able to remove the FXMLLoading from here – the application has no knowledge of how the views are loaded. If we wanted to change the view to be a normal Java class instead of FXML neither the controller, nor the application would need to be updated. Only the factory, which is responsible for creating the view, would need to be changed as you would expect.
public void start(Stage stage) throws Exception
{
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(SampleAppFactory.class);
SampleController sampleController = context.getBean(SampleController.class);
Scene scene = new Scene((Parent) sampleController.getView(), 320, 240);
scene.getStylesheets().add("fxmlapp.css");
stage.setScene(scene);
stage.setTitle("JFX2.0 Sprung");
stage.show();
}
All in all, a somewhat cleaner setup.
It’s worth noting that there are some definite limitations and disadvantages to using the traditional controller binding approach. All of these have been raised with the JFX team (in JIRA) and several are being actively explored for future releases. In my opinion, although these drawbacks are annoying, at this stage they are worth living with since by using the official controller option we don’t have to use scripting or namespaces, so our app will be easier to maintain and be better supported by RAD tools and will be easier to get help and find docco on.
For the record however the limitations to be aware of are these:
- The Controller class is specified in two places – the FXML and then cast-to in the factory. If you forget to update your FXML (very easy to do) then you will get a class cast exception in the factory. Ideally we would not have to specify the controller in the FXML at all.
- You cannot use constructor injection – since the FXML is instantiating the controller, it must have an empty constructor.
- Callback methods for button clicks must have an ActionEvent as part of the signature. This is easy to forget and will result in a runtime method. With the scripting option you did not have this restriction (the tradeoff being that you could not get the MouseEvent for mouse-style callbacks).
- You cannot use the same FXML definition with different instances of a controller, i.e. you cannot attach sample.fxml to a controller other than SampleController without duplicating the FXML file.
- You are limited to a single controller per FXML file, so you couldn’t have sample.fxml trigger callbacks in both SampleController and another controller.
- You cannot share the same controller across multiple views as the loader will create a new controller for each FXML file. You couldn’t reuse the same SampleController instance used by sample.fxml with another fxml file, there will be two instances of SampleController created.
- FXML does not support controller base classes. If your controller extends a base class and that base class has an @FXML annotated field on it, it will not get picked up by the FXMLLoader. You must define all @FXML attributes in the actual controller class itself.
There are probably a few other minor limitations but they are the big ones that I can think of. In the next post we're going to look at what happens when you have a couple of controllers and ways to share information and navigate between them.

Hey! Do you have an example based on Guice? I’ve written one, but it simply don’t work.
The start() function is:
@Override
public void start(Stage stage) throws Exception {
injector = Guice.createInjector(new MyBatisModule() {
@Override
protected void initialize() {
install(JdbcHelper.Firebird);
environmentId(“production”);
// Some MyBatis properties…
}
private Object loadController(String url) throws IOException {
InputStream fxmlStream = null;
try {
fxmlStream = getClass().getResourceAsStream(url);
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource(url));
fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
fxmlLoader.load(fxmlStream);
return fxmlLoader.getController();
} finally {
if (fxmlStream != null) {
fxmlStream.close();
}
}
}
@Provides
public MainController mainController() throws IOException {
return (MainController) loadController(“Main.fxml”);
}
@Provides
public SpedController spedController() throws IOException {
return (SpedController) loadController(“Sped.fxml”);
}
});
stage.setScene(new Scene((Parent) mainController.getViewRoot(), 500, 500));
stage.show();
}
In the MainController I have:
@Singleton
public class MainController extends AppController implements Initializable {
@FXML protected TextField testTxt;
In the SpedController I have:
@Inject private MainController mainController;
@FXML
protected void test(ActionEvent event) {
System.out.println(mainController); // This is null ???
System.out.println(mainController.testTxt);
System.out.println(“Text -> ” + mainController.testTxt.getText());
mainController.testTxt.setText(“Test!!!”);
}
Can you help me to solve this?
Thanks,
Victor (Brazil)
Hi Victor (Brazil),
I’ve just put up a blog post outlining how to do this http://www.zenjava.com/2011/10/30/better-controller-injection-with-guice
Feel free to post questions on that topic if you have any.
Cheers,
zonski (Australia)
Hi Zen Java. I’ve learned a lot from this series so far. I am using JavaFX 2.2/Java SDK 1.7.0_u5/NetBeans 7.2 Beta but your source code crashes on the first SampleApp start line
:
{code}AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(SampleAppFactory.class);{code}
with an exception:
{code} java.lang.IllegalStateException: Cannot load configuration class: com.zenjava.jfxspring.SampleAppFactory{code}
The source came directly from the link and built without any error.
I suspect that maybe the newer JavaFX 2.2 may or the JDK may be causing some incompatablity. When I run debug, break on the line above, and press “step into”, the exception occurs immediatly.
I’d really like to get this Spring/FXML combination to work to provide a decent multi-scene context without getting too cludgy. I hope there’s an update or new tweek that can solve my immediate problem.
Supplement to previous comment.
Another difference in my configuration is that my Spring library is version 3.1.1.
cglib is 1.1.
I just tried a direct copy of the link’s code for “Multiple Controllers with Shared Resources” and it threw an exception in the same location. The dump in this case was:
run:
Jul 14, 2012 3:13:23 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1c4a760: startup date [Sat Jul 14 15:13:23 EDT 2012]; root of context hierarchy
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.javafx.main.Main.launchApp(Main.java:486)
at com.javafx.main.Main.main(Main.java:638)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source)
at com.sun.javafx.application.LauncherImpl.access$000(Unknown Source)
at com.sun.javafx.application.LauncherImpl$1.run(Unknown Source)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.IllegalStateException: Cannot load configuration class: com.zenjava.jfxspring.SampleAppFactory
at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:346)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:222)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:681)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:620)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:446)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.(AnnotationConfigApplicationContext.java:73)
at com.zenjava.jfxspring.SampleApp.start(SampleApp.java:18)
at com.sun.javafx.application.LauncherImpl$5.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl$4.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl$3.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$100(Unknown Source)
at com.sun.glass.ui.win.WinApplication$2$1.run(Unknown Source)
… 1 more
I tracked down the problem–a missing class in the SpringSource collection
org.objectweb.asm.Type
This is not part of the NetBeans Spring framework plugin. It’s apparently used in the configuration process as an Enum of types. Once included, everything worked well as did the “Multiple Controllers…” project. I also did Guice versions for both of these chapters.
Thanks again for exploring these topics!