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‹.

2 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();
    }

Schreibe einen Kommentar

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