JavaFX + CDI (Weld)

Put in the class path:

The challenge is to bring FXMLLoader and CDI together, because JavaFX is creating naked objects by itself, now they have to be „CDI-aware“.

package tutego.fx;

import java.nio.charset.*;
import javafx.fxml.FXMLLoader;
import javafx.util.Callback;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;

public class FXMLLoaderProducer
{
  @Inject
  Instance<Object> instance;

  @Produces
  public FXMLLoader createLoader()
  {
    return new FXMLLoader( null, null, null, new Callback<Class<?>, Object>() {
      @Override public Object call( Class<?> param ) {
        return instance.select( param ).get();
      }
    }, StandardCharsets.UTF_8 );
  }
}

That was the hardest part.

The first regular class has the unique main(String[]) method and it’s a JavaFX application. It starts Weld, the CDI container.

package tutego.fx;

import java.io.IOException;
import javafx.application.Application;
import javafx.stage.Stage;
import org.jboss.weld.environment.se.*;

public class Main extends Application
{
  private Weld weld;

  public static void main( String[] args )
  {
    Application.launch( args );
  }

  @Override
  public void init()
  {
    weld = new Weld();
  }

  @Override
  public void start( Stage stage ) throws IOException
  {
    weld.initialize().instance().select( FxMain.class ).get().start( stage, getParameters() );
  }

  @Override
  public void stop()
  {
    weld.shutdown();
  }
}

Weld delegates to the FxMain class, the first CDI-enabled class:

package tutego.fx;

import java.io.*;
import javafx.application.Application.Parameters;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.stage.Stage;
import javax.inject.Inject;

public class FxMain
{
  @Inject
  private FXMLLoader fxmlLoader;

  public void start( Stage stage, Parameters parameters ) throws IOException
  {
    try ( InputStream fxml = RandomController.class.getResourceAsStream( "/random.fxml" ) ) {
      Parent root = (Parent) fxmlLoader.load( fxml );
      stage.setScene( new Scene( root ) );
      stage.show();
    }
  }
}

The injected FXMLLoader now has to load the FXML-file random.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="tutego.fx.RandomController">
  <children>
    <Button onAction="#onButtonClick" text="Next random" />
    <Label id="text" fx:id="label" />
  </children>
</VBox>

In the FXML-file there is a reference to the FX Controller. JavaFX has to load it and can make the injections with the help of our very first class. A regular service is getting injected into the controller:

package tutego.fx;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javax.inject.Inject;

public class RandomController
{
  @FXML
  private Label label;

  @Inject
  private RandomService randomService;

  @FXML
  public void onButtonClick()
  {
    label.setText( "Random " + randomService.nextInt() );
  }
}

The service itself is a simple singleton:

package tutego.fx;

import java.util.Random;
import javax.inject.Singleton;

@Singleton
public class RandomService
{
  private Random rnd = new Random();

  public int nextInt()
  {
    return rnd.nextInt();
  }
}

Thats it!

PS: When you start, dont forget to put a (even blank) beans.xml in META-INF.

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

3 Gedanken zu „JavaFX + CDI (Weld)

  1. Thanks for the article, it helps a lot!
    I stumbled over an issue with the shown code if I try to load an fxml file which references stylesheets via

    In this case I got an NoSuchMethodException for java.net.URL.() .

    This can be solved by handing over an instance of JavaFXBuilderFactory when creating the FXMLLoader in FXMLLoaderProducer.java :

    import java.nio.charset.StandardCharsets;

    import javafx.fxml.FXMLLoader;
    import javafx.fxml.JavaFXBuilderFactory;
    import javafx.util.Callback;

    import javax.enterprise.inject.Instance;
    import javax.enterprise.inject.Produces;
    import javax.inject.Inject;

    public class FXMLLoaderProducer {
    @Inject
    Instance instance;

    @Produces
    public FXMLLoader createLoader() {

    return new FXMLLoader(null, null, new JavaFXBuilderFactory(),
    new Callback<Class, Object>() {
    @Override
    public Object call(Class param) {
    return instance.select(param).get();
    }
    }, StandardCharsets.UTF_8);
    }
    }

  2. Nice article. Helped me a lot to get a small app that I am working on bootstrapped.
    Just one point for anyone that may hit the same issue as myself.

    I wanted to load the FXML files via a URL. Unfortunately the load(URL) on FXMLLoader is a static (which internally creates a new FXMLLoader anyways which defeats the purpose). So to avoid having to have InputStreams in my code, you can use two isntance methods, setLocation(URL url) and load():


    @Inject
    private FXMLLoader fxmlLoader;
    @Inject
    private ResourceAccessor resourceAccessor;

    public void start(Stage primaryStage)
    throws IOException {
    fxmlLoader.setLocation(resourceAccessor
    .getResource(ResourceAccessor.FXML_LANDING_PAGE));
    Parent root = (Parent) fxmlLoader.load();

    Scene scene = new Scene(root, 400, 400);
    scene.getStylesheets().add(
    resourceAccessor.getResource(ResourceAccessor.CSS_APP)
    .toExternalForm());

    primaryStage.setScene(scene);
    primaryStage.show();
    }

  3. Hi!

    How could I create a second stage, for modal dialogs? I’ve been trying to do that, but the fields of the fxml controller of the modal stage don’t get injected, they remain null

    How could be a strategy to create modal stages CDI compliant?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.