Steve On Java - JavaFX

Hacking Java, JavaFX, and Flash with Agility
  • rss
  • Home
  • NightHacking Tour
    • [Archive] NightHacking Europe – The Road to Devoxx
  • SvJugFX
  • JFXtras
    • JFXtras Individual CLA
    • JFXtras Corporate CLA
  • 2013 Travel Map
    • Let’s Meetup!
    • 2012 Travel Map
  • Contact

JavaFX in Spring Day 2 – Configuration and FXML

steveonjava | August 21, 2012

Welcome to part 2 of the JavaFX in Spring blog series. Yesterday I talked about some of the advantages of using Spring in client applications and showed how to initialize your application. For easy reference, you can flip to any of the blogs (as they are published) here:

  • JavaFX in Spring Day 1 – Application Initialization
  • JavaFX in Spring Day 2 – Configuration and FXML
  • JavaFX in Spring Day 3 – Authentication and Authorization

Today we will dig into the configuration in more detail, covering details on how you can handle FXML-based UIs. But before we get into all that work, a short aside about the San Antonio Java User Group I met earlier tonight.

It was a great user group to speak to with lots of very enthusiastic (and sharp) folks. I lightly covered the topic of Spring and JavaFX integration, and when I asked, almost everyone in the room used Spring in their day jobs. Here is a quick photo I snapped of the group after the presentation:

I am not going to embed the entire presentation, but as promised, here is the full slide deck for reference.

Now, back to the Spring configuration example, we are building out. Here is what the ScreensConfiguration class looks like for my sample Customer application:

@Configuration
@Lazy
public class ScreensConfiguration {
    private Stage primaryStage;

    public void setPrimaryStage(Stage primaryStage) {
        this.primaryStage = primaryStage;
    }

    public void showScreen(Parent screen) {
        primaryStage.setScene(new Scene(screen, 800, 500));
        primaryStage.show();
    }

    @Bean
    CustomerDataScreen customerDataScreen() {
        return new CustomerDataScreen(customerDataScreenController());
    }

    @Bean
    CustomerDataScreenController customerDataScreenController() {
        return new CustomerDataScreenController(model, this);
    }

    @Bean
    @Scope("prototype")
    AutowireFXMLDialog errorDialog() {
        return new AutowireFXMLDialog(getClass().getResource("Error.fxml"), primaryStage, StageStyle.UNDECORATED);
    }

    @Bean
    @Scope("prototype")
    AutowireFXMLDialog addUserDialog() {
        return new AutowireFXMLDialog(getClass().getResource("AddUser.fxml"), primaryStage);
    }

    @Bean
    @Scope("prototype")
    AutowireFXMLDialog loginDialog() {
        return new AutowireFXMLDialog(getClass().getResource("Login.fxml"), primaryStage, StageStyle.UNDECORATED);
    }
}

There is a lot going on here, so let me break it down in a few bullets:

  • The configuration class is annotated with @Lazy so that any bean references will only be created on demand. (Other than this, it follows the basic Spring Java Config pattern using the @Configuration and @Bean annotations.)
  • The configuration allows us to set a primary stage (which we did during initialization in CustomerApp), and then display a screen in that stage by calling showScreen.
  • Each screen and dialog in the UI is represented as a Bean class. Notice that some are declared as @Scope(“prototype”), which means a new instance will be created every time we use it. Without this annotation, the screen is a singleton so it will get created only once no matter how many times the method is called (such as customerDataScreen).

As you can see, with very little code, we are able to construct all the screens for the application and declare their behavior and dependencies. Most of these screens are FXML-based, although I did one screen/controller pair as standard JavaFX classes (the CustomerDataScreen extending StackPane). However, the much harder case is to work with FXML screens in Spring since JavaFX creates the controller for you, making bean configuration more difficult.

To encapsulate the logic for how to inject variables into the FXML controller, I created a small helper class called AutowireFXMLDialog. The source code for that is as follows:

public class AutowireFXMLDialog extends Stage {
    @Autowired
    private ApplicationContext context;

    protected final Object controller;

    public AutowireFXMLDialog(URL fxml, Window owner) {
        this(fxml, owner, StageStyle.DECORATED);
    }

    public AutowireFXMLDialog(URL fxml, Window owner, StageStyle style) {
        super(style);
        initOwner(owner);
        initModality(Modality.WINDOW_MODAL);
        FXMLLoader loader = new FXMLLoader(fxml);
        try {
            setScene(new Scene((Parent) loader.load()));
            controller = loader.getController();
            if (controller instanceof DialogController) {
                ((DialogController) controller).setDialog(this);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @PostConstruct
    private void postConstruct() {
        context.getAutowireCapableBeanFactory().autowireBeanProperties(controller, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
    }
}

Given an FXML file url as input, it does a few different things:

  • Instantiate the FXML UI and add it to the Stage.  While this implementation is oriented towards dialogs, it would be quite easy to create a version that loaded it into a reusable Node.
  • Inject the view into the controller.  We do this through a DialogController interface, which can optionally be implemented by the FXML controller class to get a reference to the view.
  • Autowire the controller class.  For any remaining dependencies defined in the controller, we let Spring do its autowiring magic to inject all of the beans and proxies.

So this seems fairly straightforward, but coming up with this magic formula was fraught with peril.  Here are some of the things that you might think are a good idea, but simply don’t work:

  1. Why can’t I autowire in the constructor? – Context is null until dependency injection happens in this class, so you would have to pass in a reference to the context (which defeats the purpose of DI)
  2. Can’t I just have Spring inject an instance to the dialog? – This seems like a good idea, but is only possible after this class is fully initialized.  For singleton beans this happens to work as long as you autowire in the PostConstruct method, but once you switch it to a prototype it fails miserably.
  3. Doesn’t that parameter say “NO” autowiring? – Probably a poor choice of name by the Spring devs, it just means that you have to explicitly declare what you want wired (with the @Autowire annotation). If you accidentally use AUTOWIRE_BY_TYPE or AUTOWIRE_BY_NAME, you are in for a world of hurt, because it will attempt to autowire the reference to this dialog, which fails in the prototype case because of [2]. Same thing applies for validation (3rd parameter), which seems to think it should check parameters that are not explicitly wired even though you defined the autowire type as AUTOWIRE_NO.

So the short summary is do it my way or you will be a sad panda.

Finally, to bring this all together, here is what the controller looks like for the ErrorDialog:

public class ErrorController implements DialogController {
    private AutowireFXMLDialog dialog;

    public void setDialog(AutowireFXMLDialog dialog) {
        this.dialog = dialog;
    }

    @FXML
    public void close() {
        dialog.close();
    }
}

Notice that it extends DialogController and gets a reference to the view injected, which makes it quite easy to dismiss the dialog when the user clicks the close button. You can also access any other Spring beans simply by prefacing them with the @Autowire annotation as we will see tomorrow when we hook up Authentication in the LoginDialog controller.

The next blog will finish out this example including the full source code so you can try it yourself.  Until then, here is a small screen capture of the finished error dialog:

 

Share this:

  • Twitter
  • Google +1
  • More
  • Facebook
  • LinkedIn
  • Email
Categories
JavaFX
Tags
configuration, fxml, JavaFX, spring
Comments rss
Comments rss
Trackback
Trackback

« JavaFX in Spring Day 1 – Application Initialization JavaFX in Spring Day 3 – Authentication and Authorization »

6 Responses to “JavaFX in Spring Day 2 – Configuration and FXML”

  1. bhangun says:
    August 25, 2012 at 4:25 pm

    I’ved tried another way using Sping to inject variable to controller. But this way is interesting.
    So I’m waiting for next blog and the full code. Thanks :)

    Reply
  2. Greg Brown says:
    August 31, 2012 at 4:20 am

    Hi Steve,

    > “the much harder case is to work with FXML screens in Spring since JavaFX creates the controller for you”

    That was true in JavaFX 2.0, but the setControllerFactory() method was added in 2.1 to address this specific case (DI frameworks). When a controller factory is specified, FXMLLoader will ask the factory to create the controller rather than instantiating it itself.

    Greg

    Reply
    • steveonjava says:
      August 31, 2012 at 9:46 am

      I didn’t realize that was added in. Will give it a try and may be able to simplify this example even more!

      Reply
      • steveonjava says:
        September 7, 2012 at 4:14 am

        I made the changes in the FXML controller injection code, and it came out a little bit cleaner… no more need to do funky autowiring after the fact now that the objects are created by Spring!

        Here is the changelist:
        https://github.com/steveonjava/JavaFX-Spring/commit/dd98a96f99c90c8ef18a7676ef5085bd921c3341

  3. mwalter says:
    December 14, 2012 at 1:11 pm

    Your full slide deck link does not work. Could you fix it please? Thanks!

    Reply
    • steveonjava says:
      December 24, 2012 at 12:47 am

      Sorry about that… fixed.

      Reply

Leave a Reply

Click here to cancel reply.

  • Travel Map - Let's Meetup

Publications

  

Affiliations

Awards

2009/2011 JavaOne Rock Star!

Disclaimer

Views and opinions expressed here are all my fault... complain to me, not my employer. :)
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox
loading Cancel
Post was not sent - check your email addresses!
Email check failed, please try again
Sorry, your blog cannot share posts by email.